"""Unit tests for ChatContextManager."""


class TestChatContextManagerBuildResearchContext:
    """Tests for ChatContextManager.build_research_context method."""

    def test_build_research_context_returns_required_keys(
        self, sample_messages, sample_accumulated_context
    ):
        """Test that build_research_context returns all required keys."""
        from src.local_deep_research.chat.context import ChatContextManager

        manager = ChatContextManager(
            session_id="session-123",
            messages=sample_messages,
            accumulated_context=sample_accumulated_context,
        )
        result = manager.build_research_context()

        # Actual return keys from implementation
        required_keys = {
            "session_id",
            "original_query",
            "accumulated_findings",
            "past_findings",
            "key_entities",
            "topics",
            "is_multi_turn",
            "turn_count",
        }
        assert required_keys.issubset(set(result.keys()))
        # past_findings must equal accumulated_findings (research engine expects this key)
        assert result["past_findings"] == result["accumulated_findings"]

    def test_build_research_context_is_multi_turn_false_for_empty_messages(
        self, sample_accumulated_context
    ):
        """Test that is_multi_turn is False when no previous messages."""
        from src.local_deep_research.chat.context import ChatContextManager

        manager = ChatContextManager(
            session_id="session-123",
            messages=[],
            accumulated_context=sample_accumulated_context,
        )
        result = manager.build_research_context()

        assert result["is_multi_turn"] is False

    def test_build_research_context_is_multi_turn_true_for_existing_messages(
        self, sample_messages, sample_accumulated_context
    ):
        """Test that is_multi_turn is True when previous messages exist."""
        from src.local_deep_research.chat.context import ChatContextManager

        manager = ChatContextManager(
            session_id="session-123",
            messages=sample_messages,
            accumulated_context=sample_accumulated_context,
        )
        result = manager.build_research_context()

        assert result["is_multi_turn"] is True

    def test_build_research_context_includes_turn_count(
        self, sample_messages, sample_accumulated_context
    ):
        """Test that turn_count is included in the context."""
        from src.local_deep_research.chat.context import ChatContextManager

        manager = ChatContextManager(
            session_id="session-123",
            messages=sample_messages,
            accumulated_context=sample_accumulated_context,
        )
        result = manager.build_research_context()

        assert result["turn_count"] == len(sample_messages)

    def test_build_research_context_includes_session_id(
        self, sample_messages, sample_accumulated_context
    ):
        """Test that session_id is included in the context."""
        from src.local_deep_research.chat.context import ChatContextManager

        manager = ChatContextManager(
            session_id="test-session-abc",
            messages=sample_messages,
            accumulated_context=sample_accumulated_context,
        )
        result = manager.build_research_context()

        assert result["session_id"] == "test-session-abc"


class TestChatContextManagerExtraction:
    """Tests for ChatContextManager extraction methods via build_research_context."""

    def test_extract_findings_from_history_only_assistant_with_research(
        self, sample_messages, sample_accumulated_context
    ):
        """Test that only assistant messages with research_id are extracted."""
        from src.local_deep_research.chat.context import ChatContextManager

        manager = ChatContextManager(
            session_id="session-123",
            messages=sample_messages,
            accumulated_context=sample_accumulated_context,
        )
        result = manager.build_research_context()

        # accumulated_findings is a string containing research content
        findings = result["accumulated_findings"]
        assert isinstance(findings, str)
        # Should contain content from assistant messages with research_id
        assert len(findings) > 0, (
            "Findings should contain assistant research content"
        )
        assert (
            "quantum" in findings.lower() or "application" in findings.lower()
        )

    def test_extract_findings_limits_to_5_recent(
        self, many_messages, sample_accumulated_context
    ):
        """Test that findings are limited to 5 most recent."""
        from src.local_deep_research.chat.context import ChatContextManager

        manager = ChatContextManager(
            session_id="session-123",
            messages=many_messages,
            accumulated_context=sample_accumulated_context,
        )
        result = manager.build_research_context()

        # accumulated_findings is a string (joined with "\n\n---\n\n")
        findings = result["accumulated_findings"]
        if findings:
            # Count separators to determine number of findings
            separator_count = findings.count("\n\n---\n\n")
            # Number of findings = separator_count + 1 (if any content)
            finding_count = separator_count + 1 if findings.strip() else 0
            assert finding_count <= 5


class TestChatContextManagerCreateSummary:
    """Tests for ChatContextManager _create_summary method via extract_context_updates."""

    def test_create_summary_finds_first_paragraph(self):
        """Test that _create_summary finds the first substantial paragraph."""
        from src.local_deep_research.chat.context import ChatContextManager

        content = """# Heading

This is the first paragraph with substantial content about quantum computing.

This is the second paragraph."""

        manager = ChatContextManager(
            session_id="session-123",
            messages=[],
            accumulated_context={},
        )
        result = manager.extract_context_updates(content)

        # summary_addition should contain first substantial paragraph
        summary = result["summary_addition"]
        # Should find first substantial paragraph (skipping header)
        assert "quantum computing" in summary.lower() or len(summary) > 0

    def test_create_summary_skips_headers(self):
        """Test that _create_summary skips markdown headers when substantial content follows."""
        from src.local_deep_research.chat.context import ChatContextManager

        # Content with a substantial paragraph (>50 chars) after headers
        content = """# Main Heading
## Sub Heading

This is the actual content paragraph with more than fifty characters of meaningful text about the topic."""

        manager = ChatContextManager(
            session_id="session-123",
            messages=[],
            accumulated_context={},
        )
        result = manager.extract_context_updates(content)

        # summary_addition should not start with # (header)
        # when there's a substantial paragraph after the headers
        summary = result["summary_addition"]
        if summary:
            assert not summary.strip().startswith("#")

    def test_create_summary_truncates_long_content(self):
        """Test that _create_summary truncates long paragraphs to 300 chars."""
        from src.local_deep_research.chat.context import ChatContextManager

        long_paragraph = "X" * 500

        manager = ChatContextManager(
            session_id="session-123",
            messages=[],
            accumulated_context={},
        )
        result = manager.extract_context_updates(long_paragraph)

        # summary_addition should be truncated
        summary = result["summary_addition"]
        assert len(summary) <= 303  # 300 chars + "..."


class TestChatContextManagerExtractContextUpdates:
    """Tests for ChatContextManager.extract_context_updates method."""

    def test_extract_context_updates_returns_required_keys(self):
        """Test that extract_context_updates returns all required keys."""
        from src.local_deep_research.chat.context import ChatContextManager

        manager = ChatContextManager(
            session_id="session-123",
            messages=[],
            accumulated_context={},
        )
        result = manager.extract_context_updates("Some new content")

        required_keys = {
            "new_entities",
            "new_topics",
            "summary_addition",
        }
        assert required_keys.issubset(set(result.keys()))


class TestChatContextManagerKeyEntitiesAndTopics:
    """Tests for key entities and topics handling."""

    def test_get_key_entities_from_accumulated_context(
        self, sample_messages, sample_accumulated_context
    ):
        """Test that key entities are retrieved from accumulated context."""
        from src.local_deep_research.chat.context import ChatContextManager

        manager = ChatContextManager(
            session_id="session-123",
            messages=sample_messages,
            accumulated_context=sample_accumulated_context,
        )
        result = manager.build_research_context()

        # Should include entities from accumulated context (limited to 20)
        assert "key_entities" in result
        assert isinstance(result["key_entities"], list)
        assert len(result["key_entities"]) <= 20

    def test_get_topics_from_accumulated_context(
        self, sample_messages, sample_accumulated_context
    ):
        """Test that topics are retrieved from accumulated context."""
        from src.local_deep_research.chat.context import ChatContextManager

        manager = ChatContextManager(
            session_id="session-123",
            messages=sample_messages,
            accumulated_context=sample_accumulated_context,
        )
        result = manager.build_research_context()

        # Should include topics from accumulated context (limited to 10)
        assert "topics" in result
        assert isinstance(result["topics"], list)
        assert len(result["topics"]) <= 10


class TestBuildResearchContextFocusedSummary:
    """build_research_context condenses prior turns into a query-focused summary."""

    def _conversation(self):
        return [
            {
                "role": "user",
                "content": "What is quantum computing?",
                "message_type": "query",
            },
            {
                "role": "assistant",
                "content": "Quantum computing uses qubits and superposition.",
                "message_type": "response",
                "research_id": "r1",
            },
        ]

    def test_focused_summary_used_as_past_findings(self, mocker):
        """With a query + snapshot, past_findings is the focused LLM summary."""
        from src.local_deep_research.chat.context import ChatContextManager

        fake_llm = mocker.Mock()
        fake_llm.invoke.return_value = mocker.Mock(
            content="Prior work, focused on cost."
        )
        get_llm = mocker.patch(
            "src.local_deep_research.config.llm_config.get_llm",
            return_value=fake_llm,
        )

        manager = ChatContextManager(
            session_id="s1",
            messages=self._conversation(),
            accumulated_context={},
            settings_snapshot={"llm.provider": "ollama"},
        )
        result = manager.build_research_context(
            current_query="How much does it cost?"
        )

        assert result["past_findings"] == "Prior work, focused on cost."
        assert result["accumulated_findings"] == "Prior work, focused on cost."
        get_llm.assert_called_once()
        # The new question must drive the focus of the summary prompt.
        prompt = fake_llm.invoke.call_args.args[0]
        assert "How much does it cost?" in prompt
        # The transcript fed to the summarizer includes both roles.
        assert "User:" in prompt and "Assistant:" in prompt

    def test_no_current_query_uses_raw_findings(self, mocker):
        """A no-arg call (no focus question) makes no LLM call."""
        from src.local_deep_research.chat.context import ChatContextManager

        get_llm = mocker.patch(
            "src.local_deep_research.config.llm_config.get_llm"
        )
        manager = ChatContextManager(
            session_id="s1",
            messages=self._conversation(),
            accumulated_context={},
            settings_snapshot={"llm.provider": "ollama"},
        )
        result = manager.build_research_context()

        get_llm.assert_not_called()
        assert "qubits" in result["past_findings"].lower()


class TestFollowupContextModes:
    """chat.followup_context_mode selects what prior context a follow-up gets."""

    def _conversation(self):
        return [
            {
                "role": "user",
                "content": "What is quantum computing?",
                "message_type": "query",
            },
            {
                "role": "assistant",
                "content": "Quantum computing uses qubits and superposition.",
                "message_type": "response",
                "research_id": "r1",
            },
        ]

    def _manager(self, mode):
        from src.local_deep_research.chat.context import ChatContextManager

        return ChatContextManager(
            session_id="s1",
            messages=self._conversation(),
            accumulated_context={},
            settings_snapshot={"chat.followup_context_mode": mode},
        )

    def test_raw_mode_uses_recent_findings_no_llm(self, mocker):
        get_llm = mocker.patch(
            "src.local_deep_research.config.llm_config.get_llm"
        )
        result = self._manager("raw").build_research_context(
            current_query="cost?"
        )

        get_llm.assert_not_called()
        assert "qubits" in result["past_findings"].lower()

    def test_full_mode_sends_whole_transcript_no_llm(self, mocker):
        get_llm = mocker.patch(
            "src.local_deep_research.config.llm_config.get_llm"
        )
        result = self._manager("full").build_research_context(
            current_query="cost?"
        )

        get_llm.assert_not_called()
        past = result["past_findings"]
        assert "User:" in past and "Assistant:" in past
        assert "What is quantum computing?" in past

    def test_none_mode_sends_no_prior_findings_no_llm(self, mocker):
        get_llm = mocker.patch(
            "src.local_deep_research.config.llm_config.get_llm"
        )
        result = self._manager("none").build_research_context(
            current_query="cost?"
        )

        get_llm.assert_not_called()
        assert result["past_findings"] == ""

    def test_summary_mode_invokes_llm(self, mocker):
        fake_llm = mocker.Mock()
        fake_llm.invoke.return_value = mocker.Mock(content="Focused summary.")
        mocker.patch(
            "src.local_deep_research.config.llm_config.get_llm",
            return_value=fake_llm,
        )
        result = self._manager("summary").build_research_context(
            current_query="cost?"
        )

        assert result["past_findings"] == "Focused summary."
        fake_llm.invoke.assert_called_once()

    def test_summary_mode_empty_when_llm_cannot_be_built(self, mocker):
        """A get_llm() failure (e.g. misconfigured provider) degrades the
        summary to empty rather than crashing the follow-up request."""
        mocker.patch(
            "src.local_deep_research.config.llm_config.get_llm",
            side_effect=RuntimeError("no provider configured"),
        )
        result = self._manager("summary").build_research_context(
            current_query="cost?"
        )

        assert result["past_findings"] == ""
