"""
Comprehensive tests for ForcedAnswerCitationHandler.
Tests initialization, analyze methods, hedging detection, and forced extraction.
"""

import pytest
from unittest.mock import Mock


class TestForcedAnswerCitationHandlerInit:
    """Tests for ForcedAnswerCitationHandler initialization."""

    def test_init_with_llm_only(self, mock_llm):
        """Test initialization with just an LLM."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        handler = ForcedAnswerCitationHandler(llm=mock_llm)

        assert handler.llm == mock_llm
        assert handler.settings_snapshot == {}

    def test_init_with_settings_snapshot(
        self, mock_llm, settings_with_fact_checking
    ):
        """Test initialization with settings snapshot."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        handler = ForcedAnswerCitationHandler(
            llm=mock_llm, settings_snapshot=settings_with_fact_checking
        )

        assert handler.settings_snapshot == settings_with_fact_checking


class TestForcedAnswerNeedsAnswerExtraction:
    """Tests for _needs_answer_extraction method."""

    @pytest.fixture
    def handler(self, mock_llm):
        """Create a ForcedAnswerCitationHandler."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        return ForcedAnswerCitationHandler(llm=mock_llm)

    def test_detects_cannot_determine(self, handler):
        """Test detection of 'cannot determine' hedging."""
        content = "Based on the available information, I cannot determine the exact answer."
        query = "What is the answer?"

        assert handler._needs_answer_extraction(content, query) is True

    def test_detects_unable_to_find(self, handler):
        """Test detection of 'unable to find' hedging."""
        content = "I was unable to find specific information about this topic."
        query = "What is the answer?"

        assert handler._needs_answer_extraction(content, query) is True

    def test_detects_insufficient(self, handler):
        """Test detection of 'insufficient' hedging."""
        content = (
            "There is insufficient evidence to provide a definitive answer."
        )
        query = "What happened?"

        assert handler._needs_answer_extraction(content, query) is True

    def test_detects_unclear(self, handler):
        """Test detection of 'unclear' hedging."""
        content = "The sources make it unclear what the actual outcome was."
        query = "What was the result?"

        assert handler._needs_answer_extraction(content, query) is True

    def test_detects_not_enough(self, handler):
        """Test detection of 'not enough' hedging."""
        content = "There is not enough information in the sources."
        query = "Who won?"

        assert handler._needs_answer_extraction(content, query) is True

    def test_detects_cannot_provide(self, handler):
        """Test detection of 'cannot provide' hedging."""
        content = "I cannot provide a specific answer based on the sources."
        query = "What is the name?"

        assert handler._needs_answer_extraction(content, query) is True

    def test_detects_no_specific_answer(self, handler):
        """Test detection of 'no specific answer' hedging."""
        content = "The sources offer no specific answer to this question."
        query = "Where did it happen?"

        assert handler._needs_answer_extraction(content, query) is True

    def test_detects_cannot_definitively(self, handler):
        """Test detection of 'cannot definitively' hedging."""
        content = "I cannot definitively state what the answer is."
        query = "When did this occur?"

        assert handler._needs_answer_extraction(content, query) is True

    def test_accepts_confident_response(self, handler):
        """Test acceptance of confident response without hedging."""
        content = (
            "The answer is John Smith. According to [1], he was the founder."
        )
        query = "What is the answer?"

        assert handler._needs_answer_extraction(content, query) is False

    def test_detects_direct_question_without_answer(self, handler):
        """Test detection of direct question type without proper answer format."""
        content = "The topic has been studied extensively by researchers."
        query = "Who invented the telephone?"

        assert handler._needs_answer_extraction(content, query) is True

    def test_accepts_what_question_with_is_answer(self, handler):
        """Test acceptance of 'what' question with 'is' in response."""
        content = "It is the Eiffel Tower. The tower was built in 1889."
        query = "What is the tallest structure in Paris?"

        assert handler._needs_answer_extraction(content, query) is False

    def test_accepts_who_question_with_was_answer(self, handler):
        """Test acceptance of 'who' question with 'was' in response."""
        content = "He was Albert Einstein. Einstein developed the theory."
        query = "Who discovered relativity?"

        assert handler._needs_answer_extraction(content, query) is False

    def test_accepts_response_with_colon_format(self, handler):
        """Test acceptance of response with colon format."""
        content = "The answer: Paris is the capital of France."
        query = "What is the capital of France?"

        assert handler._needs_answer_extraction(content, query) is False

    def test_case_insensitive_hedging_detection(self, handler):
        """Test case-insensitive detection of hedging phrases."""
        content = "CANNOT DETERMINE the answer from these sources."
        query = "What is it?"

        assert handler._needs_answer_extraction(content, query) is True


class TestForcedAnswerExtractDirectAnswer:
    """Tests for _extract_direct_answer method."""

    @pytest.fixture
    def handler(self, mock_llm):
        """Create a ForcedAnswerCitationHandler."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        return ForcedAnswerCitationHandler(llm=mock_llm)

    def test_extract_direct_answer_calls_llm(self, handler, mock_llm):
        """Test that _extract_direct_answer calls LLM."""
        mock_llm.invoke.return_value = Mock(content="Extracted Answer")

        handler._extract_direct_answer(
            "Who is it?", "Some content", "Sources text"
        )

        mock_llm.invoke.assert_called_once()

    def test_extract_direct_answer_formats_result(self, handler, mock_llm):
        """Test that extracted answer is formatted correctly."""
        mock_llm.invoke.return_value = Mock(content="John Smith")

        result = handler._extract_direct_answer(
            "Who is it?", "Original content", "Sources text"
        )

        assert result.startswith("John Smith.")
        assert "Original content" in result

    def test_extract_direct_answer_handles_exception(self, handler, mock_llm):
        """Test graceful handling of extraction exceptions."""
        mock_llm.invoke.side_effect = Exception("LLM error")

        result = handler._extract_direct_answer(
            "Who is it?", "Original content", "Sources text"
        )

        assert "most likely answer" in result
        assert "Original content" in result

    def test_extract_direct_answer_empty_returns_content(
        self, handler, mock_llm
    ):
        """An empty LLM answer (None) returns content unchanged, not '. <content>'."""
        mock_llm.invoke.return_value = None

        result = handler._extract_direct_answer(
            "Who is it?", "Original content", "Sources text"
        )

        assert result == "Original content"
        assert not result.startswith(". ")

    def test_extract_direct_answer_strips_think_tags(self, handler, mock_llm):
        """_extract_direct_answer strips <think> reasoning from the answer."""
        mock_llm.invoke.return_value = Mock(
            content="<think>weighing candidates</think>John Smith"
        )

        result = handler._extract_direct_answer(
            "Who is it?", "Original content", "Sources text"
        )

        assert "<think>" not in result
        assert "weighing candidates" not in result
        assert result.startswith("John Smith.")

    def test_extract_direct_answer_handles_string_response(
        self, handler, mock_llm
    ):
        """_extract_direct_answer handles a raw string LLM return."""
        mock_llm.invoke.return_value = "John Smith"

        result = handler._extract_direct_answer(
            "Who is it?", "Original content", "Sources text"
        )

        assert result.startswith("John Smith.")
        assert "Original content" in result

    def test_extract_direct_answer_prompt_includes_query(
        self, handler, mock_llm
    ):
        """Test that extraction prompt includes the query."""
        mock_llm.invoke.return_value = Mock(content="Answer")

        handler._extract_direct_answer(
            "Who invented the telephone?", "Content", "Sources"
        )

        call_args = mock_llm.invoke.call_args[0][0]
        assert "Who invented the telephone?" in call_args

    def test_extract_direct_answer_truncates_long_content(
        self, handler, mock_llm
    ):
        """Test that long content is truncated in prompt."""
        mock_llm.invoke.return_value = Mock(content="Answer")
        long_content = "x" * 3000

        handler._extract_direct_answer("Query?", long_content, "Sources")

        call_args = mock_llm.invoke.call_args[0][0]
        # Content should be truncated to 1500 chars
        assert len(call_args) < len(long_content)


class TestForcedAnswerAnalyzeInitial:
    """Tests for analyze_initial method."""

    def test_analyze_initial_returns_content_and_documents(
        self, mock_llm, sample_search_results
    ):
        """Test analyze_initial returns proper structure."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        handler = ForcedAnswerCitationHandler(llm=mock_llm)

        result = handler.analyze_initial(
            "What is the answer?", sample_search_results
        )

        assert "content" in result
        assert "documents" in result
        assert len(result["documents"]) == 3

    def test_analyze_initial_strips_think_tags(
        self, mock_llm, sample_search_results
    ):
        """analyze_initial strips <think> reasoning (no extraction triggered)."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        mock_llm.invoke.return_value = Mock(
            content="<think>weighing options</think>The answer is X. According to [1], this is correct."
        )

        handler = ForcedAnswerCitationHandler(llm=mock_llm)

        result = handler.analyze_initial(
            "What is the answer?", sample_search_results
        )

        assert "<think>" not in result["content"]
        assert "weighing options" not in result["content"]
        assert result["content"].startswith("The answer is X.")
        # Clean answer -> no forced extraction call
        assert mock_llm.invoke.call_count == 1

    def test_analyze_initial_prompt_includes_forced_instructions(
        self, mock_llm, sample_search_results
    ):
        """Test analyze_initial prompt includes forced answer instructions."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        # Use response that won't trigger extraction
        mock_llm.invoke.return_value = Mock(
            content="The answer is X. According to [1], this is correct."
        )

        handler = ForcedAnswerCitationHandler(llm=mock_llm)

        handler.analyze_initial("What is the answer?", sample_search_results)

        # Check the first call (initial prompt, not extraction)
        call_args = mock_llm.invoke.call_args_list[0][0][0]
        assert "DIRECT answer" in call_args
        assert "NEVER say" in call_args

    def test_analyze_initial_triggers_extraction_on_hedging(
        self, mock_llm, sample_search_results
    ):
        """Test analyze_initial triggers extraction when response hedges."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        # First call returns hedging, second call returns extracted answer
        mock_llm.invoke.side_effect = [
            Mock(content="I cannot determine the answer from these sources."),
            Mock(content="Extracted Answer"),
        ]

        handler = ForcedAnswerCitationHandler(llm=mock_llm)

        result = handler.analyze_initial("Who is it?", sample_search_results)

        # Should have called LLM twice (initial + extraction)
        assert mock_llm.invoke.call_count == 2
        assert "Extracted Answer" in result["content"]

    def test_analyze_initial_no_extraction_on_confident_response(
        self, mock_llm, sample_search_results
    ):
        """Test analyze_initial skips extraction on confident response."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        mock_llm.invoke.return_value = Mock(
            content="The answer is John Smith. According to [1]..."
        )

        handler = ForcedAnswerCitationHandler(llm=mock_llm)

        handler.analyze_initial("Who is it?", sample_search_results)

        # Should have called LLM only once
        assert mock_llm.invoke.call_count == 1

    def test_analyze_initial_handles_string_response(
        self, mock_llm_string_response, sample_search_results
    ):
        """Test analyze_initial handles string LLM response."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        handler = ForcedAnswerCitationHandler(llm=mock_llm_string_response)

        result = handler.analyze_initial("What is it?", sample_search_results)

        assert "content" in result


class TestForcedAnswerAnalyzeFollowup:
    """Tests for analyze_followup method."""

    def test_analyze_followup_returns_content_and_documents(
        self, mock_llm, sample_search_results, sample_previous_knowledge
    ):
        """Test analyze_followup returns proper structure."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        handler = ForcedAnswerCitationHandler(llm=mock_llm)

        result = handler.analyze_followup(
            "Follow-up question?",
            sample_search_results,
            sample_previous_knowledge,
            nr_of_links=3,
        )

        assert "content" in result
        assert "documents" in result

    def test_analyze_followup_with_fact_checking(
        self, mock_llm, sample_search_results, sample_previous_knowledge
    ):
        """Test analyze_followup performs fact-checking when enabled."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        handler = ForcedAnswerCitationHandler(
            llm=mock_llm,
            settings_snapshot={"general.enable_fact_checking": True},
        )

        handler.analyze_followup(
            "Follow-up question?",
            sample_search_results,
            sample_previous_knowledge,
            nr_of_links=0,
        )

        # Should call LLM at least twice (fact-check + main)
        assert mock_llm.invoke.call_count >= 2

    def test_analyze_followup_prompt_includes_critical_instructions(
        self, mock_llm, sample_search_results, sample_previous_knowledge
    ):
        """Test analyze_followup prompt includes critical forced answer instructions."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        # Use response that won't trigger extraction
        mock_llm.invoke.return_value = Mock(
            content="John Smith is the person. According to [1], he was the founder."
        )

        handler = ForcedAnswerCitationHandler(
            llm=mock_llm,
            settings_snapshot={"general.enable_fact_checking": False},
        )

        handler.analyze_followup(
            "Who is the person?",
            sample_search_results,
            sample_previous_knowledge,
            nr_of_links=0,
        )

        # Check the first call (main prompt, not extraction)
        call_args = mock_llm.invoke.call_args_list[0][0][0]
        assert "MUST start with a direct" in call_args
        assert "wrong answer is better than no answer" in call_args

    def test_analyze_followup_triggers_extraction_on_hedging(
        self, mock_llm, sample_search_results, sample_previous_knowledge
    ):
        """Test analyze_followup triggers extraction when response hedges."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        # Fact-check returns content, main response hedges, extraction returns answer
        mock_llm.invoke.side_effect = [
            Mock(content="No inconsistencies found."),  # fact-check
            Mock(content="I cannot determine who this person is."),  # main
            Mock(content="John Smith"),  # extraction
        ]

        handler = ForcedAnswerCitationHandler(
            llm=mock_llm,
            settings_snapshot={"general.enable_fact_checking": True},
        )

        result = handler.analyze_followup(
            "Who is the person?",
            sample_search_results,
            sample_previous_knowledge,
            nr_of_links=0,
        )

        assert mock_llm.invoke.call_count == 3
        assert "John Smith" in result["content"]

    def test_analyze_followup_handles_string_response(
        self,
        mock_llm_string_response,
        sample_search_results,
        sample_previous_knowledge,
    ):
        """analyze_followup returns raw string content unchanged (fact-check off, no extraction)."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        handler = ForcedAnswerCitationHandler(
            llm=mock_llm_string_response,
            settings_snapshot={"general.enable_fact_checking": False},
        )

        result = handler.analyze_followup(
            "Tell me more about the topic.",
            sample_search_results,
            sample_previous_knowledge,
            nr_of_links=0,
        )

        assert result["content"] == "Test string response with citation [1]."
        assert mock_llm_string_response.invoke.call_count == 1

    def test_analyze_followup_string_response_with_fact_checking(
        self,
        mock_llm_string_response,
        sample_search_results,
        sample_previous_knowledge,
        settings_with_fact_checking,
    ):
        """With fact-check on, string fact-check + main responses pass through; no extraction."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        handler = ForcedAnswerCitationHandler(
            llm=mock_llm_string_response,
            settings_snapshot=settings_with_fact_checking,
        )

        result = handler.analyze_followup(
            "Tell me more about the topic.",
            sample_search_results,
            sample_previous_knowledge,
            nr_of_links=0,
        )

        assert result["content"] == "Test string response with citation [1]."
        assert mock_llm_string_response.invoke.call_count == 2


class TestForcedAnswerEdgeCases:
    """Tests for edge cases and error handling."""

    def test_empty_search_results(self, mock_llm, empty_search_results):
        """Test handling of empty search results."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        handler = ForcedAnswerCitationHandler(llm=mock_llm)

        result = handler.analyze_initial("What is it?", empty_search_results)

        assert result["documents"] == []

    def test_string_search_results(self, mock_llm, string_search_results):
        """Test handling of string search results."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        handler = ForcedAnswerCitationHandler(llm=mock_llm)

        result = handler.analyze_initial("What is it?", string_search_results)

        assert result["documents"] == []

    def test_with_output_instructions(
        self, mock_llm, sample_search_results, settings_with_output_instructions
    ):
        """Test that output instructions are included in prompts."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        # Use response that won't trigger extraction
        mock_llm.invoke.return_value = Mock(
            content="The answer is X. According to [1], this is correct."
        )

        handler = ForcedAnswerCitationHandler(
            llm=mock_llm, settings_snapshot=settings_with_output_instructions
        )

        handler.analyze_initial("What is it?", sample_search_results)

        # Check the first call (initial prompt, not extraction)
        call_args = mock_llm.invoke.call_args_list[0][0][0]
        assert "formal academic English" in call_args

    def test_nr_of_links_offset_applied(
        self, mock_llm, sample_search_results, sample_previous_knowledge
    ):
        """Test that nr_of_links offset is correctly applied to document indices."""
        from local_deep_research.citation_handlers.forced_answer_citation_handler import (
            ForcedAnswerCitationHandler,
        )

        handler = ForcedAnswerCitationHandler(
            llm=mock_llm,
            settings_snapshot={"general.enable_fact_checking": False},
        )

        result = handler.analyze_followup(
            "Question?",
            sample_search_results,
            sample_previous_knowledge,
            nr_of_links=10,
        )

        # First document should have index 11 (10 + 1)
        assert result["documents"][0].metadata["index"] == 11
