"""High-value tests for knowledge/followup_context_manager.py.

Covers FollowUpContextHandler: build_context, _extract_findings,
_extract_sources, _extract_metadata, summarize_for_followup,
_generate_summary, format_for_settings_snapshot, get_relevant_context_for_llm.
"""

from unittest.mock import MagicMock

from local_deep_research.advanced_search_system.knowledge.followup_context_manager import (
    FollowUpContextHandler,
)


def _make_handler(model=None):
    if model is None:
        model = MagicMock()
        model.invoke.return_value = MagicMock(content="mock summary")
    return FollowUpContextHandler(model=model)


class TestExtractFindings:
    """Test _extract_findings."""

    def test_returns_formatted_findings_when_present(self):
        handler = _make_handler()
        data = {"formatted_findings": "These are findings"}
        result = handler._extract_findings(data)
        assert "These are findings" in result

    def test_falls_back_to_report_content(self):
        """Uses report_content when formatted_findings missing."""
        handler = _make_handler()
        data = {"report_content": "Report text" * 100}
        result = handler._extract_findings(data)
        assert "Report text" in result

    def test_truncates_report_to_2000(self):
        """Report content is truncated to 2000 chars."""
        handler = _make_handler()
        data = {"report_content": "x" * 5000}
        result = handler._extract_findings(data)
        assert len(result) <= 2000

    def test_no_findings_returns_message(self):
        """Returns 'No previous findings' when nothing available."""
        handler = _make_handler()
        result = handler._extract_findings({})
        assert "No previous findings" in result


class TestExtractSources:
    """Test _extract_sources."""

    def test_deduplicates_by_url(self):
        """Sources with same URL are deduplicated."""
        handler = _make_handler()
        data = {
            "resources": [{"url": "http://a.com", "title": "A"}],
            "all_links_of_system": [{"url": "http://a.com", "title": "A dup"}],
        }
        sources = handler._extract_sources(data)
        assert len(sources) == 1

    def test_includes_sources_without_url(self):
        """Sources without URL are included (not deduplicated)."""
        handler = _make_handler()
        data = {"resources": [{"title": "No URL"}, {"title": "Also no URL"}]}
        sources = handler._extract_sources(data)
        assert len(sources) == 2

    def test_empty_data_returns_empty_list(self):
        handler = _make_handler()
        assert handler._extract_sources({}) == []

    def test_collects_from_all_fields(self):
        """Collects sources from resources, all_links_of_system, past_links."""
        handler = _make_handler()
        data = {
            "resources": [{"url": "http://a.com", "title": "A"}],
            "all_links_of_system": [{"url": "http://b.com", "title": "B"}],
            "past_links": [{"url": "http://c.com", "title": "C"}],
        }
        sources = handler._extract_sources(data)
        assert len(sources) == 3


class TestExtractMetadata:
    """Test _extract_metadata."""

    def test_extracts_expected_keys(self):
        handler = _make_handler()
        data = {
            "strategy": "source-based",
            "mode": "deep",
            "created_at": "2024-01-01",
        }
        meta = handler._extract_metadata(data)
        assert meta["strategy"] == "source-based"
        assert meta["mode"] == "deep"
        assert "research_meta" in meta

    def test_missing_keys_default_to_empty(self):
        handler = _make_handler()
        meta = handler._extract_metadata({})
        assert meta["strategy"] == ""
        assert meta["research_meta"] == {}


class TestBuildContext:
    """Test build_context."""

    def test_returns_all_expected_keys(self):
        handler = _make_handler()
        data = {
            "research_id": "r1",
            "query": "original",
            "formatted_findings": "findings",
            "report_content": "report",
            "all_links_of_system": [],
        }
        ctx = handler.build_context(data, "follow-up question")
        expected_keys = {
            "parent_research_id",
            "original_query",
            "follow_up_query",
            "past_findings",
            "past_sources",
            "key_entities",
            "summary",
            "report_content",
            "formatted_findings",
            "all_links_of_system",
            "metadata",
        }
        assert set(ctx.keys()) == expected_keys

    def test_follow_up_query_stored(self):
        handler = _make_handler()
        ctx = handler.build_context({}, "What about X?")
        assert ctx["follow_up_query"] == "What about X?"


class TestGenerateSummary:
    """Test _generate_summary and summarize_for_followup."""

    def test_empty_findings_returns_empty_string(self):
        handler = _make_handler()
        result = handler._generate_summary("", "query")
        assert result == ""

    def test_short_findings_returned_as_is(self):
        """Findings shorter than max_length returned unchanged."""
        handler = _make_handler()
        result = handler._generate_summary(
            "short text", "query", max_length=1000
        )
        assert result == "short text"

    def test_no_model_falls_back_to_truncation(self):
        """Without model, falls back to truncation."""
        handler = FollowUpContextHandler(model=None)
        long_text = "x" * 2000
        result = handler._generate_summary(long_text, "query", max_length=100)
        assert len(result) <= 104  # 100 + "..."
        assert result.endswith("...")

    def test_model_failure_falls_back_to_truncation(self):
        """When model raises, falls back to truncation."""
        model = MagicMock()
        model.invoke.side_effect = RuntimeError("LLM error")
        handler = _make_handler(model=model)
        long_text = "x" * 2000
        result = handler._generate_summary(long_text, "query", max_length=100)
        assert result.endswith("...")

    def test_summarize_for_followup_delegates(self):
        """summarize_for_followup uses _generate_summary internally."""
        handler = _make_handler()
        result = handler.summarize_for_followup(
            "findings text", "query", max_length=500
        )
        assert isinstance(result, str)


class TestFormatForSettingsSnapshot:
    """Test format_for_settings_snapshot."""

    def test_returns_minimal_metadata(self):
        handler = _make_handler()
        ctx = {"parent_research_id": "r1", "past_findings": "some findings"}
        result = handler.format_for_settings_snapshot(ctx)
        assert "followup_metadata" in result
        assert result["followup_metadata"]["is_followup"] is True
        assert result["followup_metadata"]["has_context"] is True

    def test_empty_findings_has_context_false(self):
        handler = _make_handler()
        ctx = {"parent_research_id": "r1", "past_findings": ""}
        result = handler.format_for_settings_snapshot(ctx)
        assert result["followup_metadata"]["has_context"] is False


class TestGetRelevantContextForLLM:
    """Test get_relevant_context_for_llm."""

    def test_includes_queries(self):
        handler = _make_handler()
        ctx = {
            "original_query": "orig",
            "follow_up_query": "followup",
        }
        result = handler.get_relevant_context_for_llm(ctx)
        assert "orig" in result
        assert "followup" in result

    def test_includes_entities(self):
        handler = _make_handler()
        ctx = {
            "original_query": "",
            "follow_up_query": "",
            "key_entities": ["Entity1", "Entity2"],
        }
        result = handler.get_relevant_context_for_llm(ctx)
        assert "Entity1" in result

    def test_truncates_to_max_tokens(self):
        handler = _make_handler()
        ctx = {
            "original_query": "q",
            "follow_up_query": "q",
            "summary": "x" * 50000,
        }
        result = handler.get_relevant_context_for_llm(ctx, max_tokens=100)
        assert len(result) <= 100 * 4 + 3  # max_chars + "..."

    def test_includes_source_count(self):
        handler = _make_handler()
        ctx = {
            "original_query": "",
            "follow_up_query": "",
            "past_sources": [{"url": "a"}, {"url": "b"}],
        }
        result = handler.get_relevant_context_for_llm(ctx)
        assert "2" in result
