"""
High-value pure logic tests for IterativeRefinementStrategy.

Focuses on constructor validation, _merge_results(), and _build_research_context()
without any LLM or network calls.
"""

import pytest
from unittest.mock import MagicMock, patch

from local_deep_research.advanced_search_system.strategies.iterative_refinement_strategy import (
    IterativeRefinementStrategy,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_strategy_no_init():
    """Create an IterativeRefinementStrategy with __init__ bypassed."""
    with patch.object(
        IterativeRefinementStrategy, "__init__", lambda self, *a, **kw: None
    ):
        strategy = IterativeRefinementStrategy.__new__(
            IterativeRefinementStrategy
        )
    # Set minimal attributes that _merge_results / _build_research_context may need
    strategy.initial_strategy = MagicMock()
    strategy.initial_strategy.__class__.__name__ = "MockStrategy"
    strategy.progress_callback = None
    strategy.refinement_history = []
    return strategy


def _make_strategy_via_constructor(**overrides):
    """Create an IterativeRefinementStrategy through the real constructor."""
    mock_model = MagicMock()
    mock_search = MagicMock()
    mock_initial = MagicMock()
    mock_initial.__class__.__name__ = "MockStrategy"

    defaults = {
        "model": mock_model,
        "search": mock_search,
        "initial_strategy": mock_initial,
    }
    defaults.update(overrides)
    return IterativeRefinementStrategy(**defaults)


# ===========================================================================
# Constructor validation tests
# ===========================================================================


class TestConstructorValidation:
    """Tests for parameter validation in __init__."""

    def test_confidence_threshold_below_zero_raises(self):
        with pytest.raises(ValueError, match="confidence_threshold"):
            _make_strategy_via_constructor(confidence_threshold=-0.1)

    def test_confidence_threshold_above_one_raises(self):
        with pytest.raises(ValueError, match="confidence_threshold"):
            _make_strategy_via_constructor(confidence_threshold=1.01)

    def test_confidence_threshold_zero_accepted(self):
        strategy = _make_strategy_via_constructor(confidence_threshold=0.0)
        assert strategy.confidence_threshold == 0.0

    def test_confidence_threshold_one_accepted(self):
        strategy = _make_strategy_via_constructor(confidence_threshold=1.0)
        assert strategy.confidence_threshold == 1.0

    def test_confidence_threshold_mid_accepted(self):
        strategy = _make_strategy_via_constructor(confidence_threshold=0.5)
        assert strategy.confidence_threshold == 0.5

    def test_max_refinements_negative_raises(self):
        with pytest.raises(ValueError, match="max_refinements"):
            _make_strategy_via_constructor(max_refinements=-1)

    def test_max_refinements_zero_accepted(self):
        strategy = _make_strategy_via_constructor(max_refinements=0)
        assert strategy.max_refinements == 0

    def test_max_refinements_positive_accepted(self):
        strategy = _make_strategy_via_constructor(max_refinements=5)
        assert strategy.max_refinements == 5

    def test_evaluation_frequency_zero_raises(self):
        with pytest.raises(ValueError, match="evaluation_frequency"):
            _make_strategy_via_constructor(evaluation_frequency=0)

    def test_evaluation_frequency_negative_raises(self):
        with pytest.raises(ValueError, match="evaluation_frequency"):
            _make_strategy_via_constructor(evaluation_frequency=-2)

    def test_evaluation_frequency_one_accepted(self):
        strategy = _make_strategy_via_constructor(evaluation_frequency=1)
        assert strategy.evaluation_frequency == 1

    def test_max_evaluation_tokens_zero_raises(self):
        with pytest.raises(ValueError, match="max_evaluation_tokens"):
            _make_strategy_via_constructor(max_evaluation_tokens=0)

    def test_max_evaluation_tokens_negative_raises(self):
        with pytest.raises(ValueError, match="max_evaluation_tokens"):
            _make_strategy_via_constructor(max_evaluation_tokens=-10)

    def test_max_evaluation_tokens_none_accepted(self):
        strategy = _make_strategy_via_constructor(max_evaluation_tokens=None)
        assert strategy.max_evaluation_tokens is None

    def test_max_evaluation_tokens_positive_accepted(self):
        strategy = _make_strategy_via_constructor(max_evaluation_tokens=500)
        assert strategy.max_evaluation_tokens == 500

    def test_defaults_stored_correctly(self):
        strategy = _make_strategy_via_constructor()
        assert strategy.max_refinements == 3
        assert strategy.confidence_threshold == 0.8
        assert strategy.evaluation_frequency == 1
        assert strategy.max_evaluation_tokens is None
        assert strategy.refinement_history == []


# ===========================================================================
# _merge_results tests
# ===========================================================================


class TestMergeResults:
    """Tests for _merge_results() pure dict merging logic."""

    def test_findings_appended_with_separator(self):
        strategy = _make_strategy_no_init()
        original = {"findings": [{"content": "A"}]}
        followup = {"findings": [{"content": "B"}]}
        merged = strategy._merge_results(original, followup, "follow query")

        # Should have original finding + separator + followup finding = 3
        assert len(merged["findings"]) == 3
        assert merged["findings"][0]["content"] == "A"
        assert merged["findings"][1]["type"] == "refinement_separator"
        assert "follow query" in merged["findings"][1]["phase"]
        assert merged["findings"][2]["content"] == "B"

    def test_findings_created_when_missing_in_original(self):
        strategy = _make_strategy_no_init()
        original = {}
        followup = {"findings": [{"content": "new"}]}
        merged = strategy._merge_results(original, followup, "q")

        assert "findings" in merged
        # separator + new finding
        assert len(merged["findings"]) == 2

    def test_no_findings_key_when_followup_lacks_findings(self):
        strategy = _make_strategy_no_init()
        original = {"findings": [{"content": "old"}]}
        followup = {}
        merged = strategy._merge_results(original, followup, "q")

        # Original findings preserved, nothing appended
        assert len(merged["findings"]) == 1

    def test_formatted_findings_concatenation(self):
        strategy = _make_strategy_no_init()
        original = {"formatted_findings": "Initial research text"}
        followup = {"formatted_findings": "Follow-up text"}
        merged = strategy._merge_results(original, followup, "my query")

        result = merged["formatted_findings"]
        assert "Initial research text" in result
        assert "Follow-up text" in result
        assert "---" in result
        assert "## Refinement: my query" in result

    def test_formatted_findings_with_empty_original(self):
        strategy = _make_strategy_no_init()
        original = {}
        followup = {"formatted_findings": "New findings"}
        merged = strategy._merge_results(original, followup, "q")

        assert "New findings" in merged["formatted_findings"]
        assert "## Refinement: q" in merged["formatted_findings"]

    def test_current_knowledge_updated_to_followup(self):
        strategy = _make_strategy_no_init()
        original = {"current_knowledge": "old knowledge"}
        followup = {"current_knowledge": "new knowledge"}
        merged = strategy._merge_results(original, followup, "q")

        assert merged["current_knowledge"] == "new knowledge"

    def test_current_knowledge_preserved_when_followup_lacks_it(self):
        strategy = _make_strategy_no_init()
        original = {"current_knowledge": "old knowledge"}
        followup = {}
        merged = strategy._merge_results(original, followup, "q")

        assert merged["current_knowledge"] == "old knowledge"

    def test_questions_by_iteration_offset_merge(self):
        strategy = _make_strategy_no_init()
        original = {"questions_by_iteration": {1: ["q1"], 2: ["q2"]}}
        followup = {"questions_by_iteration": {1: ["fq1"], 2: ["fq2"]}}
        merged = strategy._merge_results(original, followup, "q")

        qbi = merged["questions_by_iteration"]
        # max key in original is 2, so followup keys offset by 2
        assert qbi[1] == ["q1"]
        assert qbi[2] == ["q2"]
        assert qbi[3] == ["fq1"]  # 2 + 1
        assert qbi[4] == ["fq2"]  # 2 + 2

    def test_questions_by_iteration_created_when_missing_in_original(self):
        strategy = _make_strategy_no_init()
        original = {}
        followup = {"questions_by_iteration": {1: ["fq1"]}}
        merged = strategy._merge_results(original, followup, "q")

        qbi = merged["questions_by_iteration"]
        # max of empty dict = 0, so offset is 0
        assert qbi[1] == ["fq1"]

    def test_questions_by_iteration_empty_original_dict(self):
        strategy = _make_strategy_no_init()
        original = {"questions_by_iteration": {}}
        followup = {"questions_by_iteration": {1: ["fq1"]}}
        merged = strategy._merge_results(original, followup, "q")

        # Empty dict -> max_iter = 0, so key = 0 + 1 = 1
        assert merged["questions_by_iteration"][1] == ["fq1"]

    def test_merge_does_not_mutate_original(self):
        strategy = _make_strategy_no_init()
        original = {
            "findings": [{"content": "A"}],
            "formatted_findings": "text",
        }
        followup = {
            "findings": [{"content": "B"}],
            "formatted_findings": "new",
        }
        strategy._merge_results(original, followup, "q")

        # The original dict's top-level keys should still be present
        # (shallow copy means top-level is not mutated)
        assert "findings" in original
        assert original["formatted_findings"] == "text"

    def test_merge_empty_dicts(self):
        strategy = _make_strategy_no_init()
        merged = strategy._merge_results({}, {}, "q")
        assert isinstance(merged, dict)

    def test_separator_finding_truncates_long_query(self):
        strategy = _make_strategy_no_init()
        long_query = "x" * 200
        original = {"findings": []}
        followup = {"findings": [{"content": "B"}]}
        merged = strategy._merge_results(original, followup, long_query)

        separator = merged["findings"][0]
        # The phase truncates to first 100 chars
        assert len(separator["phase"]) <= len("Refinement: ") + 100


# ===========================================================================
# _build_research_context tests
# ===========================================================================


class TestBuildResearchContext:
    """Tests for _build_research_context() pure dict construction."""

    def test_basic_context_fields(self):
        strategy = _make_strategy_no_init()
        results = {
            "formatted_findings": "some findings",
            "all_links_of_system": [{"url": "http://a.com"}],
        }
        ctx = strategy._build_research_context(results, "test query")

        assert ctx["original_query"] == "test query"
        assert ctx["past_findings"] == "some findings"
        assert ctx["past_sources"] == [{"url": "http://a.com"}]
        assert ctx["resources"] == [{"url": "http://a.com"}]

    def test_delegate_strategy_name_cleaned(self):
        strategy = _make_strategy_no_init()
        strategy.initial_strategy.__class__.__name__ = "ParallelSearchStrategy"
        ctx = strategy._build_research_context({}, "q")

        # Code does .replace("Strategy", "").replace("SearchStrategy", "").lower()
        # "ParallelSearchStrategy" -> "ParallelSearch" -> "ParallelSearch" -> "parallelsearch"
        assert ctx["delegate_strategy"] == "parallelsearch"

    def test_delegate_strategy_name_strategy_suffix(self):
        strategy = _make_strategy_no_init()
        strategy.initial_strategy.__class__.__name__ = "StandardStrategy"
        ctx = strategy._build_research_context({}, "q")

        assert ctx["delegate_strategy"] == "standard"

    def test_questions_by_iteration_included_when_present(self):
        strategy = _make_strategy_no_init()
        results = {"questions_by_iteration": {1: ["q1"]}}
        ctx = strategy._build_research_context(results, "q")

        assert ctx["questions_by_iteration"] == {1: ["q1"]}

    def test_questions_by_iteration_absent_when_not_in_results(self):
        strategy = _make_strategy_no_init()
        ctx = strategy._build_research_context({}, "q")

        assert "questions_by_iteration" not in ctx

    def test_findings_included_when_present(self):
        strategy = _make_strategy_no_init()
        results = {"findings": [{"content": "finding1"}]}
        ctx = strategy._build_research_context(results, "q")

        assert ctx["findings"] == [{"content": "finding1"}]

    def test_findings_absent_when_not_in_results(self):
        strategy = _make_strategy_no_init()
        ctx = strategy._build_research_context({}, "q")

        assert "findings" not in ctx

    def test_defaults_for_missing_keys(self):
        strategy = _make_strategy_no_init()
        ctx = strategy._build_research_context({}, "q")

        assert ctx["past_findings"] == ""
        assert ctx["past_sources"] == []
        assert ctx["resources"] == []

    def test_context_with_all_fields_populated(self):
        strategy = _make_strategy_no_init()
        results = {
            "formatted_findings": "full findings",
            "all_links_of_system": [{"url": "http://x.com"}],
            "questions_by_iteration": {1: ["a"], 2: ["b"]},
            "findings": [{"content": "c"}],
        }
        ctx = strategy._build_research_context(results, "full query")

        assert ctx["original_query"] == "full query"
        assert ctx["past_findings"] == "full findings"
        assert ctx["past_sources"] == [{"url": "http://x.com"}]
        assert ctx["resources"] == [{"url": "http://x.com"}]
        assert ctx["questions_by_iteration"] == {1: ["a"], 2: ["b"]}
        assert ctx["findings"] == [{"content": "c"}]
        assert "delegate_strategy" in ctx
