"""
Pure-logic tests for FollowUpContextHandler helper methods.

Tests _extract_findings, _extract_sources, _extract_metadata,
format_for_settings_snapshot, and get_relevant_context_for_llm —
no LLM calls (only tests methods that don't invoke self.model).
"""

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


def _handler():
    """Build handler with model=None to prevent accidental LLM calls."""
    return FollowUpContextHandler(model=None)


# ---------------------------------------------------------------------------
# _extract_findings
# ---------------------------------------------------------------------------


class TestExtractFindings:
    """Verify findings extraction from research data."""

    def test_returns_formatted_findings(self):
        """Prefers formatted_findings field."""
        h = _handler()
        data = {"formatted_findings": "Finding A\nFinding B"}
        result = h._extract_findings(data)
        assert "Finding A" in result
        assert "Finding B" in result

    def test_falls_back_to_report_content(self):
        """Uses report_content when formatted_findings absent."""
        h = _handler()
        data = {"report_content": "This is the report content"}
        result = h._extract_findings(data)
        assert "This is the report content" in result

    def test_report_truncated_to_2000(self):
        """Report content fallback is truncated to 2000 chars."""
        h = _handler()
        long_report = "x" * 5000
        data = {"report_content": long_report}
        result = h._extract_findings(data)
        assert len(result) <= 2000

    def test_both_fields_uses_formatted(self):
        """When both exist, formatted_findings is primary."""
        h = _handler()
        data = {
            "formatted_findings": "formatted",
            "report_content": "report",
        }
        result = h._extract_findings(data)
        assert "formatted" in result

    def test_no_findings_returns_message(self):
        """Empty data returns fallback message."""
        h = _handler()
        result = h._extract_findings({})
        assert result == "No previous findings available"

    def test_empty_formatted_findings_falls_back(self):
        """Empty string formatted_findings falls back to report."""
        h = _handler()
        data = {"formatted_findings": "", "report_content": "report text"}
        result = h._extract_findings(data)
        assert "report text" in result


# ---------------------------------------------------------------------------
# _extract_sources
# ---------------------------------------------------------------------------


class TestExtractSources:
    """Verify source extraction and deduplication."""

    def test_empty_data_returns_empty(self):
        """No source fields returns empty list."""
        h = _handler()
        result = h._extract_sources({})
        assert result == []

    def test_deduplicates_by_url(self):
        """Sources with same URL are deduplicated."""
        h = _handler()
        data = {
            "resources": [
                {"url": "https://a.com", "title": "First"},
                {"url": "https://a.com", "title": "Duplicate"},
            ]
        }
        result = h._extract_sources(data)
        assert len(result) == 1

    def test_includes_sources_without_url(self):
        """Sources without URL field are included."""
        h = _handler()
        data = {"resources": [{"title": "No URL Source"}]}
        result = h._extract_sources(data)
        assert len(result) == 1

    def test_collects_from_all_fields(self):
        """Sources are collected from resources, all_links_of_system, past_links."""
        h = _handler()
        data = {
            "resources": [{"url": "https://a.com"}],
            "all_links_of_system": [{"url": "https://b.com"}],
            "past_links": [{"url": "https://c.com"}],
        }
        result = h._extract_sources(data)
        urls = [s["url"] for s in result]
        assert "https://a.com" in urls
        assert "https://b.com" in urls
        assert "https://c.com" in urls

    def test_cross_field_dedup(self):
        """Same URL in different fields is deduplicated."""
        h = _handler()
        data = {
            "resources": [{"url": "https://a.com"}],
            "past_links": [{"url": "https://a.com"}],
        }
        result = h._extract_sources(data)
        assert len(result) == 1

    def test_empty_url_not_deduplicated(self):
        """Sources with empty URL strings are included separately."""
        h = _handler()
        data = {
            "resources": [
                {"url": "", "title": "A"},
                {"url": "", "title": "B"},
            ]
        }
        result = h._extract_sources(data)
        # Empty URLs are treated as "no URL" and included
        assert len(result) == 2


# ---------------------------------------------------------------------------
# _extract_metadata
# ---------------------------------------------------------------------------


class TestExtractMetadata:
    """Verify metadata extraction."""

    def test_extracts_expected_keys(self):
        """Returns strategy, mode, created_at, research_meta."""
        h = _handler()
        data = {
            "strategy": "evidence_based",
            "mode": "deep",
            "created_at": "2024-01-01",
            "research_meta": {"iterations": 5},
        }
        result = h._extract_metadata(data)
        assert result["strategy"] == "evidence_based"
        assert result["mode"] == "deep"
        assert result["created_at"] == "2024-01-01"
        assert result["research_meta"]["iterations"] == 5

    def test_missing_keys_default_to_empty(self):
        """Missing keys default to empty string/dict."""
        h = _handler()
        result = h._extract_metadata({})
        assert result["strategy"] == ""
        assert result["mode"] == ""
        assert result["created_at"] == ""
        assert result["research_meta"] == {}


# ---------------------------------------------------------------------------
# format_for_settings_snapshot
# ---------------------------------------------------------------------------


class TestFormatForSettingsSnapshot:
    """Verify settings snapshot formatting."""

    def test_returns_minimal_metadata(self):
        """Returns minimal metadata dict."""
        h = _handler()
        context = {
            "parent_research_id": "r123",
            "past_findings": "some findings",
        }
        result = h.format_for_settings_snapshot(context)
        assert result["followup_metadata"]["parent_research_id"] == "r123"
        assert result["followup_metadata"]["is_followup"] is True
        assert result["followup_metadata"]["has_context"] is True

    def test_empty_findings_has_context_false(self):
        """Empty findings sets has_context to False."""
        h = _handler()
        context = {"parent_research_id": "r1", "past_findings": ""}
        result = h.format_for_settings_snapshot(context)
        assert result["followup_metadata"]["has_context"] is False

    def test_missing_parent_id(self):
        """Missing parent_research_id returns None."""
        h = _handler()
        result = h.format_for_settings_snapshot({})
        assert result["followup_metadata"]["parent_research_id"] is None


# ---------------------------------------------------------------------------
# get_relevant_context_for_llm
# ---------------------------------------------------------------------------


class TestGetRelevantContextForLlm:
    """Verify LLM context string generation."""

    def test_includes_queries(self):
        """Output includes original and follow-up queries."""
        h = _handler()
        context = {
            "original_query": "What is X?",
            "follow_up_query": "Tell me more about Y",
        }
        result = h.get_relevant_context_for_llm(context)
        assert "What is X?" in result
        assert "Tell me more about Y" in result

    def test_includes_summary(self):
        """Summary is included when present."""
        h = _handler()
        context = {
            "original_query": "",
            "follow_up_query": "",
            "summary": "Key finding: X is related to Y",
        }
        result = h.get_relevant_context_for_llm(context)
        assert "Key finding: X is related to Y" in result

    def test_includes_entities(self):
        """Key entities are listed."""
        h = _handler()
        context = {
            "original_query": "",
            "follow_up_query": "",
            "key_entities": ["Alice", "Bob", "Paris"],
        }
        result = h.get_relevant_context_for_llm(context)
        assert "Alice" in result
        assert "Bob" in result
        assert "Paris" in result

    def test_includes_source_count(self):
        """Source count is shown."""
        h = _handler()
        context = {
            "original_query": "",
            "follow_up_query": "",
            "past_sources": [{"url": "a"}, {"url": "b"}],
        }
        result = h.get_relevant_context_for_llm(context)
        assert "2" in result

    def test_truncates_to_max_tokens(self):
        """Output is truncated to max_tokens * 4 chars."""
        h = _handler()
        context = {
            "original_query": "",
            "follow_up_query": "",
            "summary": "x" * 10000,
        }
        result = h.get_relevant_context_for_llm(context, max_tokens=100)
        assert len(result) <= 400 + 10  # Allow for "..." suffix

    def test_entities_capped_at_five(self):
        """Only first 5 entities are shown."""
        h = _handler()
        entities = [f"Entity{i}" for i in range(10)]
        context = {
            "original_query": "",
            "follow_up_query": "",
            "key_entities": entities,
        }
        result = h.get_relevant_context_for_llm(context)
        assert "Entity4" in result
        assert "Entity5" not in result


# ---------------------------------------------------------------------------
# _generate_summary (no-model fallback paths)
# ---------------------------------------------------------------------------


class TestGenerateSummaryFallback:
    """Test _generate_summary without a model (fallback paths)."""

    def test_empty_findings_returns_empty(self):
        """Empty findings returns empty string."""
        h = _handler()
        result = h._generate_summary(findings="", query="test")
        assert result == ""

    def test_no_model_truncates_with_max_length(self):
        """Without model, truncates findings to max_length."""
        h = _handler()
        findings = "a" * 1000
        result = h._generate_summary(
            findings=findings, query="test", max_length=200
        )
        assert len(result) <= 204  # 200 + "..."

    def test_no_model_default_truncation(self):
        """Without model and no max_length, truncates to 500."""
        h = _handler()
        findings = "b" * 1000
        result = h._generate_summary(findings=findings, query="test")
        assert len(result) <= 504  # 500 + "..."

    def test_short_findings_returned_as_is(self):
        """Findings shorter than max_length are returned unchanged."""
        h = _handler()
        findings = "Short text"
        result = h._generate_summary(
            findings=findings, query="test", max_length=100
        )
        assert result == "Short text"
