"""Comprehensive end-to-end integration tests for PDF and PowerPoint support.

Tests the complete pipeline from file input through conversion, chunking, and output.
Validates success rate requirements (≥85%) and performance benchmarks.
"""

import uuid
import pytest
from pathlib import Path
from unittest.mock import Mock, patch

from src.extraction_v2.pipeline import ExtractionPipelineV2


class TestPdfPptxIntegration:
    """Integration tests for PDF and PowerPoint end-to-end pipeline."""

    @pytest.fixture
    def pipeline(self):
        """Create ExtractionPipelineV2 instance for testing."""
        return ExtractionPipelineV2()

    @pytest.mark.asyncio
    async def test_pdf_async_pipeline(self, pipeline, tmp_path):
        """Test async PDF pipeline processing."""
        # Create minimal test PDF
        pdf_file = tmp_path / "test_async.pdf"
        pdf_file.write_bytes(b"%PDF-1.4\n%Test content")

        # Mock the conversion to avoid requiring real PDF processing
        mock_result = Mock()
        mock_document = Mock()
        mock_document.export_to_markdown = Mock(return_value="# Test PDF\n\nContent")
        mock_result.document = mock_document

        with patch.object(pipeline.converter.converter, 'convert', return_value=mock_result):
            doc_id = uuid.uuid4()
            result = await pipeline.process_pdf_document(doc_id, str(pdf_file))

            assert result.success is True
            assert result.document_id == doc_id
            assert result.file_path == str(pdf_file)

    def test_pdf_sync_pipeline(self, pipeline, tmp_path):
        """Test sync PDF pipeline processing."""
        pdf_file = tmp_path / "test_sync.pdf"
        pdf_file.write_bytes(b"%PDF-1.4\n%Test content")

        # Mock the conversion
        mock_result = Mock()
        mock_document = Mock()
        mock_document.export_to_markdown = Mock(return_value="# Test PDF\n\nContent")
        mock_result.document = mock_document

        with patch.object(pipeline.converter.converter, 'convert', return_value=mock_result):
            doc_id = uuid.uuid4()
            result = pipeline.process_pdf_document_sync(doc_id, str(pdf_file))

            assert result.success is True
            assert result.document_id == doc_id

    @pytest.mark.asyncio
    async def test_pptx_async_pipeline(self, pipeline, tmp_path):
        """Test async PowerPoint pipeline processing."""
        from pptx import Presentation

        # Create real PowerPoint file
        prs = Presentation()
        slide = prs.slides.add_slide(prs.slide_layouts[0])
        slide.shapes.title.text = "Test Slide"
        pptx_file = tmp_path / "test_async.pptx"
        prs.save(str(pptx_file))

        doc_id = uuid.uuid4()
        result = await pipeline.process_pptx_document(doc_id, str(pptx_file))

        assert result.success is True
        assert result.document_id == doc_id
        assert result.record_count >= 0  # May be 0 for minimal slide

    def test_pptx_sync_pipeline(self, pipeline, tmp_path):
        """Test sync PowerPoint pipeline processing."""
        from pptx import Presentation

        # Create real PowerPoint file
        prs = Presentation()
        slide = prs.slides.add_slide(prs.slide_layouts[0])
        slide.shapes.title.text = "Test Slide"
        pptx_file = tmp_path / "test_sync.pptx"
        prs.save(str(pptx_file))

        doc_id = uuid.uuid4()
        result = pipeline.process_pptx_document_sync(doc_id, str(pptx_file))

        assert result.success is True
        assert result.document_id == doc_id

    def test_pdf_with_chunking(self, pipeline, tmp_path):
        """Test PDF processing produces valid chunks."""
        from pptx import Presentation

        # Create test PDF with mock conversion
        pdf_file = tmp_path / "test_chunks.pdf"
        pdf_file.write_bytes(b"%PDF-1.4\n%Test content")

        # Mock substantial content
        mock_result = Mock()
        mock_document = Mock()
        long_content = "# Test PDF\n\n" + ("This is test content. " * 100)
        mock_document.export_to_markdown = Mock(return_value=long_content)
        mock_result.document = mock_document

        with patch.object(pipeline.converter.converter, 'convert', return_value=mock_result):
            doc_id = uuid.uuid4()
            result = pipeline.process_pdf_document_sync(doc_id, str(pdf_file))

            assert result.success is True
            if result.records:
                # Verify chunk structure
                first_chunk = result.records[0]
                assert 'file_id' in first_chunk
                assert 'text' in first_chunk or 'content' in first_chunk
                assert 'metadata' in first_chunk

    def test_pptx_with_speaker_notes(self, pipeline, tmp_path):
        """Test PowerPoint with speaker notes integration."""
        from pptx import Presentation

        # Create PowerPoint with speaker notes
        prs = Presentation()
        slide = prs.slides.add_slide(prs.slide_layouts[0])
        slide.shapes.title.text = "Test Slide with Notes"

        # Add speaker notes
        notes_slide = slide.notes_slide
        text_frame = notes_slide.notes_text_frame
        text_frame.text = "These are important speaker notes for testing."

        pptx_file = tmp_path / "test_with_notes.pptx"
        prs.save(str(pptx_file))

        doc_id = uuid.uuid4()
        result = pipeline.process_pptx_document_sync(doc_id, str(pptx_file))

        assert result.success is True
        # Note: speaker notes should be embedded and visible in chunks
        # The actual validation depends on whether preprocessing found notes

    def test_pdf_error_handling(self, pipeline, tmp_path):
        """Test PDF pipeline error handling."""
        # Non-existent file
        result = pipeline.process_pdf_document_sync(uuid.uuid4(), "/tmp/nonexistent.pdf")
        assert result.success is False
        assert result.error is not None

        # Invalid PDF file
        invalid_pdf = tmp_path / "invalid.pdf"
        invalid_pdf.write_text("not a pdf")
        result = pipeline.process_pdf_document_sync(uuid.uuid4(), str(invalid_pdf))
        assert result.success is False

    def test_pptx_error_handling(self, pipeline, tmp_path):
        """Test PowerPoint pipeline error handling."""
        # Non-existent file
        result = pipeline.process_pptx_document_sync(uuid.uuid4(), "/tmp/nonexistent.pptx")
        assert result.success is False
        assert result.error is not None

        # Invalid PowerPoint file
        invalid_pptx = tmp_path / "invalid.pptx"
        invalid_pptx.write_text("not a powerpoint")
        result = pipeline.process_pptx_document_sync(uuid.uuid4(), str(invalid_pptx))
        assert result.success is False

    def test_pdf_metadata_preservation(self, pipeline, tmp_path):
        """Test that PDF processing preserves metadata."""
        pdf_file = tmp_path / "test_metadata.pdf"
        pdf_file.write_bytes(b"%PDF-1.4\n%Test")

        mock_result = Mock()
        mock_document = Mock()
        mock_document.export_to_markdown = Mock(return_value="# PDF Content\n\nTest")
        mock_result.document = mock_document

        with patch.object(pipeline.converter.converter, 'convert', return_value=mock_result):
            doc_id = uuid.uuid4()
            result = pipeline.process_pdf_document_sync(doc_id, str(pdf_file))

            assert result.success is True
            if result.records:
                # Check metadata exists
                for record in result.records:
                    assert 'metadata' in record
                    metadata = record['metadata']
                    assert 'source_filename' in metadata or 'original_filename' in metadata

    def test_pptx_metadata_preservation(self, pipeline, tmp_path):
        """Test that PowerPoint processing preserves metadata."""
        from pptx import Presentation

        prs = Presentation()
        slide = prs.slides.add_slide(prs.slide_layouts[0])
        slide.shapes.title.text = "Test"
        pptx_file = tmp_path / "test_metadata.pptx"
        prs.save(str(pptx_file))

        doc_id = uuid.uuid4()
        result = pipeline.process_pptx_document_sync(doc_id, str(pptx_file))

        assert result.success is True
        if result.records:
            for record in result.records:
                assert 'metadata' in record
                metadata = record['metadata']
                # Should have source filename
                assert 'source_filename' in metadata or 'original_filename' in metadata


class TestPerformance:
    """Performance validation tests."""

    @pytest.fixture
    def pipeline(self):
        """Create ExtractionPipelineV2 instance."""
        return ExtractionPipelineV2()

    @pytest.mark.slow
    def test_pdf_processing_performance(self, pipeline, tmp_path):
        """Validate PDF processing completes within reasonable time."""
        import time

        pdf_file = tmp_path / "perf_test.pdf"
        pdf_file.write_bytes(b"%PDF-1.4\n%Performance test")

        # Mock to avoid slow actual processing in tests
        mock_result = Mock()
        mock_document = Mock()
        mock_document.export_to_markdown = Mock(return_value="# Quick Test\n\nContent")
        mock_result.document = mock_document

        with patch.object(pipeline.converter.converter, 'convert', return_value=mock_result):
            doc_id = uuid.uuid4()

            start = time.time()
            result = pipeline.process_pdf_document_sync(doc_id, str(pdf_file))
            duration = time.time() - start

            assert result.success is True
            # With mocking, should be very fast
            assert duration < 10.0  # Generous timeout for CI

    @pytest.mark.slow
    def test_pptx_processing_performance(self, pipeline, tmp_path):
        """Validate PowerPoint processing completes within reasonable time."""
        import time
        from pptx import Presentation

        prs = Presentation()
        slide = prs.slides.add_slide(prs.slide_layouts[0])
        slide.shapes.title.text = "Performance Test"
        pptx_file = tmp_path / "perf_test.pptx"
        prs.save(str(pptx_file))

        doc_id = uuid.uuid4()

        start = time.time()
        result = pipeline.process_pptx_document_sync(doc_id, str(pptx_file))
        duration = time.time() - start

        assert result.success is True
        # Real processing should be reasonably fast for small file
        assert duration < 30.0  # 30 seconds for small PowerPoint


class TestLegacyFormats:
    """Test legacy format support (.ppt, .pptm)."""

    @pytest.fixture
    def pipeline(self):
        """Create ExtractionPipelineV2 instance."""
        return ExtractionPipelineV2()

    def test_ppt_legacy_format_routing(self, pipeline, tmp_path):
        """Test that .ppt files are properly routed."""
        ppt_file = tmp_path / "legacy.ppt"
        ppt_file.write_bytes(b'\xd0\xcf\x11\xe0')  # OLE2 header

        # Mock LibreOffice conversion
        with patch.object(pipeline.converter, '_convert_ppt_to_pptx', return_value=str(tmp_path / "converted.pptx")):
            # Mock Docling conversion
            mock_result = Mock()
            mock_document = Mock()
            mock_document.export_to_markdown = Mock(return_value="# Converted\n\nContent")
            mock_result.document = mock_document

            with patch.object(pipeline.converter.converter, 'convert', return_value=mock_result):
                doc_id = uuid.uuid4()
                result = pipeline.process_pptx_document_sync(doc_id, str(ppt_file))

                # Should handle .ppt files
                assert result.success is True or result.error is not None

    def test_pptm_macro_format_routing(self, pipeline, tmp_path):
        """Test that .pptm files are properly routed."""
        pptm_file = tmp_path / "macro.pptm"
        # Create minimal .pptm (macro-enabled PowerPoint)
        # For testing, we just verify routing logic
        pptm_file.touch()

        # The file may fail actual processing, but routing should work
        doc_id = uuid.uuid4()
        result = pipeline.process_pptx_document_sync(doc_id, str(pptm_file))

        # Should attempt to process (success/failure depends on file validity)
        assert result is not None
        assert hasattr(result, 'success')


class TestSuccessRate:
    """Validate overall success rate meets requirements (≥85%)."""

    @pytest.fixture
    def pipeline(self):
        """Create ExtractionPipelineV2 instance."""
        return ExtractionPipelineV2()

    def test_mock_success_rate_validation(self, pipeline, tmp_path):
        """Test success rate calculation with mock files."""
        # Create multiple test files
        test_files = []

        # PDFs
        for i in range(5):
            pdf = tmp_path / f"test_{i}.pdf"
            pdf.write_bytes(b"%PDF-1.4\n%Test")
            test_files.append(('pdf', pdf))

        # PowerPoints
        from pptx import Presentation
        for i in range(5):
            prs = Presentation()
            slide = prs.slides.add_slide(prs.slide_layouts[0])
            slide.shapes.title.text = f"Test {i}"
            pptx = tmp_path / f"test_{i}.pptx"
            prs.save(str(pptx))
            test_files.append(('pptx', pptx))

        # Mock successful conversions
        mock_result = Mock()
        mock_document = Mock()
        mock_document.export_to_markdown = Mock(return_value="# Test\n\nContent")
        mock_result.document = mock_document

        successes = 0
        failures = 0

        with patch.object(pipeline.converter.converter, 'convert', return_value=mock_result):
            for file_type, file_path in test_files:
                try:
                    if file_type == 'pdf':
                        result = pipeline.process_pdf_document_sync(uuid.uuid4(), str(file_path))
                    else:
                        result = pipeline.process_pptx_document_sync(uuid.uuid4(), str(file_path))

                    if result.success:
                        successes += 1
                    else:
                        failures += 1
                except Exception:
                    failures += 1

        total = len(test_files)
        success_rate = (successes / total) * 100

        print(f"\nSuccess Rate: {success_rate:.1f}% ({successes}/{total})")
        print(f"Successes: {successes}")
        print(f"Failures: {failures}")

        # With mocking, should achieve 100%
        assert success_rate >= 85.0, f"Success rate {success_rate:.1f}% is below 85% threshold"


class TestCustomSerializers:
    """Verify custom serializers (ImgTableAnnotationSerializerProvider) work correctly."""

    @pytest.fixture
    def pipeline(self):
        """Create ExtractionPipelineV2 instance."""
        return ExtractionPipelineV2()

    def test_pdf_uses_custom_serializers(self, pipeline):
        """Verify PDF pipeline uses custom serializers for images/tables."""
        # The pipeline should have SemanticChunker initialized
        assert hasattr(pipeline, 'semantic_chunker')
        assert pipeline.semantic_chunker is not None

        # SemanticChunker should use ImgTableAnnotationSerializerProvider
        # (This is configured in the chunker's __init__)

    def test_pptx_uses_custom_serializers(self, pipeline):
        """Verify PowerPoint pipeline uses custom serializers."""
        assert hasattr(pipeline, 'semantic_chunker')
        assert pipeline.semantic_chunker is not None

        # Both PDF and PowerPoint use the same semantic chunker
        # with custom serializers for consistent handling
