"""Tests for CitationHandler factory logic, settings extraction, and delegation."""

from unittest.mock import MagicMock, patch

import pytest

from local_deep_research.citation_handler import CitationHandler

# ---------------------------------------------------------------------------
# Patch targets -- the handler classes are lazily imported inside
# _create_handler(), so we patch them at their source modules.
# ---------------------------------------------------------------------------
STANDARD_PATH = (
    "local_deep_research.citation_handlers."
    "standard_citation_handler.StandardCitationHandler"
)
FORCED_PATH = (
    "local_deep_research.citation_handlers."
    "forced_answer_citation_handler.ForcedAnswerCitationHandler"
)
PRECISION_PATH = (
    "local_deep_research.citation_handlers."
    "precision_extraction_handler.PrecisionExtractionHandler"
)


@pytest.fixture
def mock_llm():
    return MagicMock(name="mock_llm")


# ── Handler type selection ────────────────────────────────────────────────


class TestHandlerTypeSelection:
    """Verify that the correct handler implementation is instantiated."""

    @patch(STANDARD_PATH)
    def test_default_no_handler_type_no_settings(self, mock_cls, mock_llm):
        """No handler_type and no settings -> StandardCitationHandler."""
        handler = CitationHandler(mock_llm)
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot={})
        assert handler._handler is mock_cls.return_value

    @patch(STANDARD_PATH)
    def test_handler_type_standard(self, mock_cls, mock_llm):
        """Explicit handler_type='standard' -> StandardCitationHandler."""
        handler = CitationHandler(mock_llm, handler_type="standard")
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot={})
        assert handler._handler is mock_cls.return_value

    @patch(FORCED_PATH)
    def test_handler_type_forced(self, mock_cls, mock_llm):
        """handler_type='forced' -> ForcedAnswerCitationHandler."""
        handler = CitationHandler(mock_llm, handler_type="forced")
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot={})
        assert handler._handler is mock_cls.return_value

    @patch(FORCED_PATH)
    def test_handler_type_forced_answer(self, mock_cls, mock_llm):
        """handler_type='forced_answer' -> ForcedAnswerCitationHandler."""
        handler = CitationHandler(mock_llm, handler_type="forced_answer")
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot={})
        assert handler._handler is mock_cls.return_value

    @patch(FORCED_PATH)
    def test_handler_type_browsecomp(self, mock_cls, mock_llm):
        """handler_type='browsecomp' -> ForcedAnswerCitationHandler."""
        handler = CitationHandler(mock_llm, handler_type="browsecomp")
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot={})
        assert handler._handler is mock_cls.return_value

    @patch(PRECISION_PATH)
    def test_handler_type_precision(self, mock_cls, mock_llm):
        """handler_type='precision' -> PrecisionExtractionHandler."""
        handler = CitationHandler(mock_llm, handler_type="precision")
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot={})
        assert handler._handler is mock_cls.return_value

    @patch(PRECISION_PATH)
    def test_handler_type_precision_extraction(self, mock_cls, mock_llm):
        """handler_type='precision_extraction' -> PrecisionExtractionHandler."""
        handler = CitationHandler(mock_llm, handler_type="precision_extraction")
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot={})
        assert handler._handler is mock_cls.return_value

    @patch(PRECISION_PATH)
    def test_handler_type_simpleqa(self, mock_cls, mock_llm):
        """handler_type='simpleqa' -> PrecisionExtractionHandler."""
        handler = CitationHandler(mock_llm, handler_type="simpleqa")
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot={})
        assert handler._handler is mock_cls.return_value

    @patch(STANDARD_PATH)
    def test_unknown_handler_type_falls_back_to_standard(
        self, mock_cls, mock_llm
    ):
        """Unknown handler_type -> StandardCitationHandler with a warning."""
        handler = CitationHandler(mock_llm, handler_type="unknown_type")
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot={})
        assert handler._handler is mock_cls.return_value

    @patch(STANDARD_PATH)
    def test_handler_type_is_case_insensitive(self, mock_cls, mock_llm):
        """handler_type='STANDARD' (uppercase) -> StandardCitationHandler."""
        handler = CitationHandler(mock_llm, handler_type="STANDARD")
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot={})
        assert handler._handler is mock_cls.return_value


# ── Settings snapshot extraction ──────────────────────────────────────────


class TestSettingsSnapshotExtraction:
    """Verify that handler_type is correctly extracted from settings_snapshot."""

    @patch(FORCED_PATH)
    def test_handler_type_from_settings_string_value(self, mock_cls, mock_llm):
        """settings_snapshot with plain string value for citation.handler_type."""
        snapshot = {"citation.handler_type": "forced"}
        handler = CitationHandler(mock_llm, settings_snapshot=snapshot)
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot=snapshot)
        assert handler._handler is mock_cls.return_value

    @patch(FORCED_PATH)
    def test_handler_type_from_settings_dict_with_value_key(
        self, mock_cls, mock_llm
    ):
        """settings_snapshot with dict {'value': 'forced'} structure."""
        snapshot = {"citation.handler_type": {"value": "forced"}}
        handler = CitationHandler(mock_llm, settings_snapshot=snapshot)
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot=snapshot)
        assert handler._handler is mock_cls.return_value

    @patch(PRECISION_PATH)
    def test_explicit_handler_type_overrides_settings(self, mock_cls, mock_llm):
        """Explicit handler_type takes precedence over settings_snapshot."""
        snapshot = {"citation.handler_type": "standard"}
        handler = CitationHandler(
            mock_llm, handler_type="precision", settings_snapshot=snapshot
        )
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot=snapshot)
        assert handler._handler is mock_cls.return_value

    @patch(STANDARD_PATH)
    def test_settings_snapshot_none_defaults_to_empty_dict(
        self, mock_cls, mock_llm
    ):
        """settings_snapshot=None is treated as {}."""
        handler = CitationHandler(mock_llm, settings_snapshot=None)
        mock_cls.assert_called_once_with(mock_llm, settings_snapshot={})
        assert handler.settings_snapshot == {}


# ── Delegation ────────────────────────────────────────────────────────────


class TestDelegation:
    """Verify that public methods and attributes delegate to the inner handler."""

    @patch(STANDARD_PATH)
    def test_analyze_initial_delegates(self, mock_cls, mock_llm):
        """analyze_initial forwards to _handler.analyze_initial."""
        mock_inner = mock_cls.return_value
        mock_inner.analyze_initial.return_value = {"result": "initial"}

        handler = CitationHandler(mock_llm)
        result = handler.analyze_initial(
            "test query", [{"url": "http://example.com"}]
        )

        mock_inner.analyze_initial.assert_called_once_with(
            "test query", [{"url": "http://example.com"}]
        )
        assert result == {"result": "initial"}

    @patch(STANDARD_PATH)
    def test_analyze_followup_delegates_with_all_args(self, mock_cls, mock_llm):
        """analyze_followup forwards all arguments to _handler.analyze_followup."""
        mock_inner = mock_cls.return_value
        mock_inner.analyze_followup.return_value = {"result": "followup"}

        handler = CitationHandler(mock_llm)
        result = handler.analyze_followup(
            "follow-up question",
            [{"url": "http://example.com"}],
            "previous knowledge text",
            5,
        )

        mock_inner.analyze_followup.assert_called_once_with(
            "follow-up question",
            [{"url": "http://example.com"}],
            "previous knowledge text",
            5,
        )
        assert result == {"result": "followup"}

    @patch(STANDARD_PATH)
    def test_create_documents_exposed_from_handler(self, mock_cls, mock_llm):
        """_create_documents is exposed from the inner handler."""
        mock_inner = mock_cls.return_value
        handler = CitationHandler(mock_llm)
        assert handler._create_documents is mock_inner._create_documents

    @patch(STANDARD_PATH)
    def test_format_sources_exposed_from_handler(self, mock_cls, mock_llm):
        """_format_sources is exposed from the inner handler."""
        mock_inner = mock_cls.return_value
        handler = CitationHandler(mock_llm)
        assert handler._format_sources is mock_inner._format_sources
