"""Unit tests for PowerPoint conversion in DocumentToHtmlConverter."""

import tempfile
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock

import pytest
from pptx import Presentation
from pptx.util import Inches

from src.extraction_v2.document_converter import DocumentToHtmlConverter
from src.extraction_v2.pptx_preprocessor import PptxPreprocessor


@pytest.fixture
def converter():
    """Create DocumentToHtmlConverter instance."""
    return DocumentToHtmlConverter()


@pytest.fixture
def preprocessor():
    """Create PptxPreprocessor instance."""
    return PptxPreprocessor()


@pytest.fixture
def sample_pptx(tmp_path):
    """Create a sample PowerPoint file for testing."""
    prs = Presentation()

    # Slide 1: Title slide
    title_slide_layout = prs.slide_layouts[0]
    slide = prs.slides.add_slide(title_slide_layout)
    title = slide.shapes.title
    subtitle = slide.placeholders[1]
    title.text = "Test Presentation"
    subtitle.text = "Unit Test Sample"

    # Slide 2: Content slide
    bullet_slide_layout = prs.slide_layouts[1]
    slide = prs.slides.add_slide(bullet_slide_layout)
    shapes = slide.shapes
    title_shape = shapes.title
    body_shape = shapes.placeholders[1]
    title_shape.text = "Test Content"
    tf = body_shape.text_frame
    tf.text = "First bullet point"
    p = tf.add_paragraph()
    p.text = "Second bullet point"

    # Save
    pptx_path = tmp_path / "test_sample.pptx"
    prs.save(str(pptx_path))
    return pptx_path


@pytest.fixture
def sample_pptx_with_notes(tmp_path):
    """Create a sample PowerPoint file with speaker notes."""
    prs = Presentation()

    # Slide with speaker notes
    title_slide_layout = prs.slide_layouts[0]
    slide = prs.slides.add_slide(title_slide_layout)
    title = slide.shapes.title
    title.text = "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 this slide."

    # Save
    pptx_path = tmp_path / "test_with_notes.pptx"
    prs.save(str(pptx_path))
    return pptx_path


class TestPptxPreprocessor:
    """Test PptxPreprocessor speaker notes extraction."""

    def test_preprocess_no_notes(self, preprocessor, sample_pptx):
        """Test preprocessing PowerPoint with no speaker notes."""
        result_path = preprocessor.preprocess(sample_pptx)

        assert result_path.exists()
        assert result_path.name == f"{sample_pptx.stem}_preprocessed.pptx"

        # Verify no notes were embedded
        prs = Presentation(str(result_path))
        assert len(prs.slides) == 2

        # Clean up
        result_path.unlink()

    def test_preprocess_with_notes(self, preprocessor, sample_pptx_with_notes):
        """Test preprocessing PowerPoint with speaker notes."""
        result_path = preprocessor.preprocess(sample_pptx_with_notes)

        assert result_path.exists()
        assert result_path.name == f"{sample_pptx_with_notes.stem}_preprocessed.pptx"

        # Verify notes were embedded as text boxes
        prs = Presentation(str(result_path))
        slide = prs.slides[0]

        # Find the embedded notes text box
        found_notes = False
        for shape in slide.shapes:
            if hasattr(shape, 'text') and '[SPEAKER NOTES]:' in shape.text:
                found_notes = True
                assert 'important speaker notes' in shape.text
                break

        assert found_notes, "Speaker notes should be embedded as text box"

        # Clean up
        result_path.unlink()

    def test_extract_and_embed_notes_count(self, preprocessor, sample_pptx_with_notes):
        """Test that notes count is accurate."""
        prs = Presentation(str(sample_pptx_with_notes))
        notes_count = preprocessor._extract_and_embed_notes(prs)

        assert notes_count == 1


class TestPowerPointConversion:
    """Test PowerPoint to Markdown conversion."""

    def test_convert_pptx_to_markdown_no_notes(self, converter, sample_pptx):
        """Test PPTX conversion without speaker notes."""
        markdown = converter._convert_pptx_to_markdown(str(sample_pptx))

        assert markdown is not None
        assert len(markdown) > 0
        assert "Test Presentation" in markdown or "Test Content" in markdown

    def test_convert_pptx_to_markdown_with_notes(self, converter, sample_pptx_with_notes):
        """Test PPTX conversion with speaker notes - should preprocess."""
        markdown = converter._convert_pptx_to_markdown(str(sample_pptx_with_notes))

        assert markdown is not None
        assert len(markdown) > 0
        # Notes should be embedded in the markdown output
        assert "SPEAKER NOTES" in markdown or "speaker notes" in markdown.lower()

    def test_convert_pptx_skips_preprocessing_when_no_notes(self, converter, sample_pptx):
        """Test that preprocessing is skipped when no speaker notes exist."""
        with patch('src.extraction_v2.pptx_preprocessor.PptxPreprocessor') as mock_preprocessor:
            markdown = converter._convert_pptx_to_markdown(str(sample_pptx))

            # Preprocessor should NOT be called when no notes exist
            mock_preprocessor.assert_not_called()
            assert markdown is not None

    def test_convert_pptx_preprocesses_when_notes_exist(self, converter, sample_pptx_with_notes):
        """Test that preprocessing happens when speaker notes exist."""
        with patch('src.extraction_v2.pptx_preprocessor.PptxPreprocessor') as MockPreprocessor:
            # Setup mock
            mock_instance = MagicMock()
            MockPreprocessor.return_value = mock_instance
            mock_instance.preprocess.return_value = sample_pptx_with_notes

            converter._convert_pptx_to_markdown(str(sample_pptx_with_notes))

            # Preprocessor should be called when notes exist
            MockPreprocessor.assert_called_once()
            mock_instance.preprocess.assert_called_once()

    def test_convert_pptx_handles_docling_failure(self, converter, tmp_path):
        """Test error handling when Docling fails."""
        # Create invalid PPTX
        invalid_pptx = tmp_path / "invalid.pptx"
        invalid_pptx.write_text("not a real pptx file")

        result = converter._convert_pptx_to_markdown(str(invalid_pptx))
        assert result is None

    def test_convert_pptx_cleans_up_temp_files(self, converter, sample_pptx_with_notes):
        """Test that temporary preprocessed files are cleaned up."""
        markdown = converter._convert_pptx_to_markdown(str(sample_pptx_with_notes))

        assert markdown is not None

        # Check no _preprocessed.pptx files left behind
        preprocessed_files = list(sample_pptx_with_notes.parent.glob("*_preprocessed.pptx"))
        assert len(preprocessed_files) == 0, "Preprocessed files should be cleaned up"


class TestLegacyPowerPointConversion:
    """Test .ppt and .pptm conversion to .pptx."""

    def test_convert_ppt_to_pptx_libreoffice_not_found(self, converter, tmp_path):
        """Test error handling when LibreOffice is not found."""
        with patch.object(converter, '_find_libreoffice_command', return_value=None):
            ppt_file = tmp_path / "test.ppt"
            ppt_file.touch()

            result = converter._convert_ppt_to_pptx(ppt_file)
            assert result is None

    @patch('subprocess.run')
    @patch('builtins.open', create=True)
    @patch('fcntl.flock')
    def test_convert_ppt_to_pptx_success(self, mock_flock, mock_open_func, mock_subprocess, converter, tmp_path):
        """Test successful .ppt to .pptx conversion."""
        # Mock LibreOffice command
        with patch.object(converter, '_find_libreoffice_command', return_value='libreoffice'):
            # Mock subprocess success
            mock_subprocess.return_value = Mock(returncode=0, stderr='')

            # Create fake .ppt file
            ppt_file = tmp_path / "test.ppt"
            ppt_file.write_bytes(b'\xd0\xcf\x11\xe0')  # OLE2 magic bytes

            # Mock the output file creation
            def side_effect(*args, **kwargs):
                if args[0] == 'pptx':
                    temp_dir = Path(tempfile.gettempdir())
                    output = temp_dir / "test.pptx"
                    output.touch()
                    return output
                return Mock()

            with patch('tempfile.mkdtemp') as mock_mkdtemp:
                mock_mkdtemp.side_effect = [
                    str(tmp_path / 'temp_output'),
                    str(tmp_path / 'temp_profile')
                ]

                # Create the expected output file
                expected_output = tmp_path / 'temp_output' / 'test.pptx'
                expected_output.parent.mkdir(exist_ok=True)
                expected_output.touch()

                result = converter._convert_ppt_to_pptx(ppt_file)

                assert result is not None
                assert Path(result).name == "test.pptx"

    @patch('subprocess.run')
    @patch('builtins.open', create=True)
    @patch('fcntl.flock')
    def test_convert_ppt_to_pptx_failure(self, mock_flock, mock_open_func, mock_subprocess, converter, tmp_path):
        """Test failed .ppt to .pptx conversion."""
        with patch.object(converter, '_find_libreoffice_command', return_value='libreoffice'):
            # Mock subprocess failure
            mock_subprocess.return_value = Mock(returncode=1, stderr='Conversion failed')

            ppt_file = tmp_path / "test.ppt"
            ppt_file.touch()

            result = converter._convert_ppt_to_pptx(ppt_file)
            assert result is None

    @patch('subprocess.run')
    @patch('builtins.open', create=True)
    @patch('fcntl.flock')
    def test_convert_ppt_to_pptx_timeout(self, mock_flock, mock_open_func, mock_subprocess, converter, tmp_path):
        """Test timeout handling in .ppt conversion."""
        from subprocess import TimeoutExpired

        with patch.object(converter, '_find_libreoffice_command', return_value='libreoffice'):
            # Mock subprocess timeout
            mock_subprocess.side_effect = TimeoutExpired('libreoffice', 60)

            ppt_file = tmp_path / "test.ppt"
            ppt_file.touch()

            result = converter._convert_ppt_to_pptx(ppt_file)
            assert result is None


class TestRoutingLogic:
    """Test PowerPoint routing in convert_to_html()."""

    def test_routing_pptx_files(self, converter, sample_pptx):
        """Test that .pptx files are routed correctly."""
        with patch.object(converter, '_convert_pptx_to_markdown', return_value='markdown content') as mock_convert:
            result = converter.convert_to_html(str(sample_pptx))

            mock_convert.assert_called_once_with(str(sample_pptx))
            assert result == 'markdown content'

    def test_routing_ppt_files(self, converter, tmp_path):
        """Test that .ppt files are routed correctly."""
        ppt_file = tmp_path / "test.ppt"
        ppt_file.touch()

        with patch.object(converter, '_convert_pptx_to_markdown', return_value='markdown content') as mock_convert:
            result = converter.convert_to_html(str(ppt_file))

            mock_convert.assert_called_once_with(str(ppt_file))
            assert result == 'markdown content'

    def test_routing_pptm_files(self, converter, tmp_path):
        """Test that .pptm files are routed correctly."""
        pptm_file = tmp_path / "test.pptm"
        pptm_file.touch()

        with patch.object(converter, '_convert_pptx_to_markdown', return_value='markdown content') as mock_convert:
            result = converter.convert_to_html(str(pptm_file))

            mock_convert.assert_called_once_with(str(pptm_file))
            assert result == 'markdown content'

    def test_unsupported_format(self, converter, tmp_path):
        """Test error handling for unsupported file formats."""
        unsupported = tmp_path / "test.txt"
        unsupported.touch()

        result = converter.convert_to_html(str(unsupported))
        assert result is None


class TestIntegration:
    """Integration tests with real Docling conversion."""

    def test_end_to_end_pptx_conversion(self, converter, sample_pptx):
        """Test full end-to-end PowerPoint conversion."""
        markdown = converter.convert_to_html(str(sample_pptx))

        assert markdown is not None
        assert len(markdown) > 50  # Should have substantial content
        assert isinstance(markdown, str)

    def test_end_to_end_with_speaker_notes(self, converter, sample_pptx_with_notes):
        """Test full conversion with speaker notes."""
        markdown = converter.convert_to_html(str(sample_pptx_with_notes))

        assert markdown is not None
        assert len(markdown) > 0
        # Check that notes were embedded and converted
        assert "notes" in markdown.lower() or "SPEAKER" in markdown
