"""Regression tests for office-document upload support (issue #4414).

ODT/DOC/DOCX/PPT/PPTX/XLS/XLSX were advertised as supported but failed at
runtime because their ``unstructured`` parser dependencies (python-docx,
python-pptx, openpyxl) were never installed. These tests assert that:

1. With the parser dependencies installed, the office extensions are
   registered and text actually round-trips through the upload extraction
   path (``extract_text_from_bytes``).
2. The registry is *honest*: a format is advertised only when its real
   runtime dependency is importable, so a missing dependency hides the
   format instead of accepting an upload that later fails silently.
"""

import importlib
import shutil
import tempfile
from pathlib import Path

import pytest

REGISTRY_MODULE = "local_deep_research.document_loaders.loader_registry"


def _reload(module_name):
    return importlib.reload(importlib.import_module(module_name))


class TestOfficeExtensionsRegistered:
    """Office formats are advertised when their parser deps are present."""

    def test_modern_office_formats_registered(self):
        # Modern office formats work with pip-only parser deps (no system
        # binaries), so they are registered whenever those deps are present.
        pytest.importorskip("docx")
        pytest.importorskip("pptx")
        pytest.importorskip("openpyxl")
        pytest.importorskip("msoffcrypto")

        from local_deep_research.document_loaders.loader_registry import (
            LOADER_REGISTRY,
        )

        for ext in (".docx", ".pptx", ".xlsx"):
            assert ext in LOADER_REGISTRY, f"{ext} should be registered"

    def test_legacy_binary_formats_gated_on_libreoffice(self):
        # .doc/.ppt are OLE binary formats that unstructured converts via
        # LibreOffice; they must be advertised only when soffice is present.
        import local_deep_research.document_loaders.loader_registry as mod

        for ext in (".doc", ".ppt"):
            assert (ext in mod.LOADER_REGISTRY) == (
                mod.HAS_LIBREOFFICE
                and (mod.HAS_DOCX_DEP if ext == ".doc" else mod.HAS_PPTX_DEP)
            )

    def test_odt_registered_when_docx_and_pandoc_present(self):
        pytest.importorskip("docx")
        pytest.importorskip("pypandoc")

        from local_deep_research.document_loaders.loader_registry import (
            LOADER_REGISTRY,
        )

        assert ".odt" in LOADER_REGISTRY


class TestOfficeExtractionRoundTrip:
    """Real files extract text through the public upload path."""

    def test_docx_extraction(self):
        pytest.importorskip("docx")
        from docx import Document

        from local_deep_research.document_loaders import (
            extract_text_from_bytes,
        )

        marker = "Hello DOCX world, extraction works."
        with tempfile.NamedTemporaryFile(suffix=".docx") as tmp:
            doc = Document()
            doc.add_paragraph(marker)
            doc.save(tmp.name)
            content = Path(tmp.name).read_bytes()

        text = extract_text_from_bytes(content, ".docx", "sample.docx")
        assert text is not None
        assert marker in text

    def test_xls_extraction(self):
        # xlwt (a dev-only writer) builds the legacy fixture; the loader reads
        # it with pandas + xlrd, bypassing the fragile unstructured path.
        xlwt = pytest.importorskip("xlwt")
        pytest.importorskip("xlrd")

        from local_deep_research.document_loaders import (
            extract_text_from_bytes,
        )

        wb = xlwt.Workbook()
        ws = wb.add_sheet("Data")
        for r, (a, b) in enumerate(
            [("Animal", "Count"), ("Otter", "3"), ("Badger", "7")]
        ):
            ws.write(r, 0, a)
            ws.write(r, 1, b)
        with tempfile.NamedTemporaryFile(suffix=".xls", delete=False) as tmp:
            xls_path = tmp.name
        try:
            wb.save(xls_path)
            content = Path(xls_path).read_bytes()
        finally:
            Path(xls_path).unlink(missing_ok=True)

        text = extract_text_from_bytes(content, ".xls", "sample.xls")
        assert text is not None
        assert "Animal" in text and "Otter" in text and "Badger" in text

    def test_odt_extraction(self):
        pypandoc = pytest.importorskip("pypandoc")
        pytest.importorskip("docx")
        if not shutil.which("pandoc"):
            try:
                pypandoc.get_pandoc_version()
            except OSError:
                pytest.skip("pandoc binary not available")

        from local_deep_research.document_loaders import (
            extract_text_from_bytes,
        )

        marker = "Hello ODT world, extraction works."
        with tempfile.NamedTemporaryFile(suffix=".odt", delete=False) as tmp:
            odt_path = tmp.name
        try:
            # Generate a genuine ODT (with styles.xml etc.) via pandoc.
            pypandoc.convert_text(
                marker, "odt", format="md", outputfile=odt_path
            )
            content = Path(odt_path).read_bytes()
        finally:
            Path(odt_path).unlink(missing_ok=True)

        text = extract_text_from_bytes(content, ".odt", "sample.odt")
        assert text is not None
        assert marker in text


class TestXLSLoaderEncryption:
    """Encrypted XLS files produce a clear error, not a generic traceback."""

    def test_encrypted_xls_raises_value_error(self, monkeypatch):
        pytest.importorskip("xlrd")

        from local_deep_research.document_loaders.xls_loader import XLSLoader

        def fake_read_excel(*args, **kwargs):
            raise Exception("Workbook is encrypted")

        monkeypatch.setattr("pandas.read_excel", fake_read_excel)

        loader = XLSLoader("/tmp/fake.xls")
        with pytest.raises(ValueError, match="encrypted"):
            loader.load()

    def test_corrupted_xls_raises_original_error(self, monkeypatch):
        pytest.importorskip("xlrd")

        from local_deep_research.document_loaders.xls_loader import XLSLoader

        def fake_read_excel(*args, **kwargs):
            raise Exception("Unsupported format")

        monkeypatch.setattr("pandas.read_excel", fake_read_excel)

        loader = XLSLoader("/tmp/fake.xls")
        with pytest.raises(Exception, match="Unsupported format"):
            loader.load()


class TestHonestDetection:
    """A missing runtime dependency hides the format from the registry.

    The registry computes its capability flags at import time, so we reload
    the module with ``importlib.util.find_spec`` patched to report specific
    modules as absent. Patching ``_module_available`` directly would not
    survive the reload (it redefines that function from source), so we patch
    the lower-level ``find_spec`` it calls.
    """

    @pytest.fixture
    def restore_module(self):
        yield
        _reload(REGISTRY_MODULE)

    @staticmethod
    def _reload_without(monkeypatch, blocked):
        import importlib.util as importlib_util

        real_find_spec = importlib_util.find_spec

        def fake_find_spec(name, *args, **kwargs):
            if name in blocked:
                return None
            return real_find_spec(name, *args, **kwargs)

        monkeypatch.setattr(importlib_util, "find_spec", fake_find_spec)
        return _reload(REGISTRY_MODULE)

    def test_missing_python_docx_hides_word_and_odt(
        self, monkeypatch, restore_module
    ):
        reloaded = self._reload_without(monkeypatch, {"docx"})

        assert reloaded.HAS_DOCX_DEP is False
        for ext in (".doc", ".docx", ".odt"):
            assert ext not in reloaded.LOADER_REGISTRY

    def test_missing_pptx_hides_powerpoint(self, monkeypatch, restore_module):
        reloaded = self._reload_without(monkeypatch, {"pptx"})

        assert reloaded.HAS_PPTX_DEP is False
        for ext in (".ppt", ".pptx"):
            assert ext not in reloaded.LOADER_REGISTRY

    def test_missing_ocr_dep_hides_image_formats(
        self, monkeypatch, restore_module
    ):
        reloaded = self._reload_without(
            monkeypatch, {"pytesseract", "unstructured.pytesseract"}
        )

        assert reloaded.HAS_OCR_DEP is False
        for ext in (".png", ".jpg", ".jpeg", ".tiff", ".tif", ".bmp", ".heic"):
            assert ext not in reloaded.LOADER_REGISTRY
