"""Edge-case tests for research_library/services/pdf_storage_manager.py."""

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

from local_deep_research.constants import (
    FILE_PATH_TEXT_ONLY,
    FILE_PATH_BLOB_DELETED,
)


def _make_manager(storage_mode="database", library_root="/tmp/test_lib"):
    """Create a PDFStorageManager for testing."""
    from local_deep_research.research_library.services.pdf_storage_manager import (
        PDFStorageManager,
    )

    return PDFStorageManager(
        library_root=Path(library_root),
        storage_mode=storage_mode,
        max_pdf_size_mb=100,
    )


class TestGenerateFilenameArxiv:
    """Tests for _generate_filename with arXiv URLs."""

    def test_generate_filename_arxiv_subdomain(self):
        """export.arxiv.org recognized via .endswith('.arxiv.org')."""
        manager = _make_manager()
        filename = manager._generate_filename(
            "https://export.arxiv.org/pdf/2301.12345", 1, "fallback.pdf"
        )
        assert "arxiv" in filename
        assert "2301.12345" in filename

    def test_generate_filename_arxiv_5digit_id(self):
        """Both 2301.1234 (4-digit) and 2401.12345 (5-digit) formats extracted."""
        manager = _make_manager()

        # 4-digit sub-ID
        fn4 = manager._generate_filename(
            "https://arxiv.org/abs/2301.1234", None, "fallback.pdf"
        )
        assert "2301.1234" in fn4

        # 5-digit sub-ID
        fn5 = manager._generate_filename(
            "https://arxiv.org/abs/2401.12345", None, "fallback.pdf"
        )
        assert "2401.12345" in fn5


class TestGenerateFilenamePMC:
    """Tests for _generate_filename with PMC URLs."""

    def test_generate_filename_pmc_exact_id(self):
        """PMC ID extracted precisely."""
        manager = _make_manager()
        filename = manager._generate_filename(
            "https://ncbi.nlm.nih.gov/pmc/articles/PMC7654321/",
            None,
            "fallback.pdf",
        )
        assert "PMC7654321" in filename
        assert filename.endswith(".pdf")


class TestHasPdfSentinel:
    """Tests for has_pdf sentinel checks."""

    def test_has_pdf_false_for_non_pdf_file_type(self):
        """has_pdf returns False for non-PDF file types before any DB query."""
        manager = _make_manager()
        document = Mock()
        document.file_type = "html"  # Not a PDF
        document.id = 1

        session = MagicMock()
        result = manager.has_pdf(document, session)
        assert result is False
        # No database query should have been made
        session.query.assert_not_called()

    def test_has_pdf_text_only_not_stored_is_sentinel(self):
        """Verify 'text_only_not_stored' is treated as a sentinel in has_pdf.

        The source code checks against FILE_PATH_SENTINELS to exclude sentinel
        values from filesystem lookup.
        """
        from local_deep_research.research_library.services.pdf_storage_manager import (
            PDFStorageManager,
        )
        import inspect

        source = inspect.getsource(PDFStorageManager._get_safe_file_path)
        # Verify the sentinel value appears in the source code's exclusion check
        assert "FILE_PATH_SENTINELS" in source


class TestInferStorageMode:
    """Tests for _infer_storage_mode edge cases."""

    def test_infer_storage_mode_text_only_not_stored(self):
        """'text_only_not_stored' sentinel is correctly treated as no storage."""
        manager = _make_manager()
        document = Mock()
        document.file_path = FILE_PATH_TEXT_ONLY
        document.blob = None

        result = manager._infer_storage_mode(document)
        assert result == "none"

    def test_infer_storage_mode_blob_deleted(self):
        """'blob_deleted' sentinel is correctly treated as no storage."""
        manager = _make_manager()
        document = Mock()
        document.file_path = FILE_PATH_BLOB_DELETED
        document.blob = None

        result = manager._infer_storage_mode(document)
        assert result == "none"


class TestSaveToDatabase:
    """Tests for _save_to_database update vs create paths."""

    def test_save_to_database_updates_existing_blob(self):
        """Update path (line 254) — updating an existing blob."""
        manager = _make_manager()
        document = Mock()
        document.id = 42

        existing_blob = Mock()
        session = MagicMock()
        session.query.return_value.filter_by.return_value.first.return_value = (
            existing_blob
        )

        pdf_content = b"%PDF-1.4 test content"
        manager._save_to_database(pdf_content, document, session)

        # Verify the existing blob was updated, not a new one created
        assert existing_blob.pdf_binary == pdf_content
        assert (
            existing_blob.blob_hash == hashlib.sha256(pdf_content).hexdigest()
        )
        # session.add should NOT have been called (update, not create)
        session.add.assert_not_called()


class TestLoadFromDatabase:
    """Tests for _load_from_database side effects."""

    def test_load_from_database_updates_last_accessed(self):
        """last_accessed timestamp side-effect verified."""
        manager = _make_manager()
        document = Mock()
        document.id = 1

        blob = Mock()
        blob.pdf_binary = b"%PDF-1.4 content"
        blob.last_accessed = None

        session = MagicMock()
        session.query.return_value.filter_by.return_value.first.return_value = (
            blob
        )

        result = manager._load_from_database(document, session)

        assert result == b"%PDF-1.4 content"
        # last_accessed should have been updated
        assert blob.last_accessed is not None


class TestUpgradeToPdf:
    """Tests for upgrade_to_pdf edge cases."""

    def test_upgrade_to_pdf_with_none_storage_mode(self):
        """Legacy None storage_mode is accepted for upgrade."""
        manager = _make_manager()
        document = Mock()
        document.id = 10
        document.storage_mode = None
        document.file_path = None

        session = MagicMock()
        # No existing blob
        session.query.return_value.filter_by.return_value.first.return_value = (
            None
        )

        pdf_content = b"%PDF-1.4 small content"

        with patch.object(manager, "_save_to_database") as mock_save:
            result = manager.upgrade_to_pdf(document, pdf_content, session)

        assert result is True
        mock_save.assert_called_once_with(pdf_content, document, session)
        assert document.storage_mode == "database"

    def test_upgrade_to_pdf_with_string_none_rejected(self):
        """String 'none' storage_mode is also accepted for upgrade."""
        manager = _make_manager()
        document = Mock()
        document.id = 11
        document.storage_mode = "none"
        document.file_path = None

        session = MagicMock()
        session.query.return_value.filter_by.return_value.first.return_value = (
            None
        )

        pdf_content = b"%PDF-1.4 small content"

        with patch.object(manager, "_save_to_database") as mock_save:
            result = manager.upgrade_to_pdf(document, pdf_content, session)

        # "none" is in (None, "none"), so upgrade proceeds
        assert result is True
        mock_save.assert_called_once()
