"""High-value pure logic tests for IterativeReasoningStrategy and KnowledgeState."""

import pytest
from unittest.mock import patch

from local_deep_research.advanced_search_system.strategies.iterative_reasoning_strategy import (
    IterativeReasoningStrategy,
    KnowledgeState,
)


def _make_strategy(**overrides):
    """Create an IterativeReasoningStrategy with mocked __init__."""
    with patch.object(
        IterativeReasoningStrategy, "__init__", lambda self, *a, **kw: None
    ):
        strat = IterativeReasoningStrategy.__new__(IterativeReasoningStrategy)
        strat.knowledge_state = KnowledgeState(original_query="test query")
        # Apply any overrides
        for key, val in overrides.items():
            setattr(strat, key, val)
        return strat


class TestKnowledgeStateDefaults:
    """Test KnowledgeState dataclass default values."""

    def test_defaults(self):
        ks = KnowledgeState(original_query="hello")
        assert ks.original_query == "hello"
        assert ks.key_facts == []
        assert ks.uncertainties == []
        assert ks.search_history == []
        assert ks.candidate_answers == []
        assert ks.confidence == 0.0
        assert ks.iteration == 0

    def test_default_lists_are_independent(self):
        ks1 = KnowledgeState(original_query="a")
        ks2 = KnowledgeState(original_query="b")
        ks1.key_facts.append("fact")
        assert ks2.key_facts == []

    def test_custom_values(self):
        ks = KnowledgeState(
            original_query="q",
            key_facts=["f1"],
            uncertainties=["u1"],
            confidence=0.5,
            iteration=3,
        )
        assert ks.key_facts == ["f1"]
        assert ks.confidence == 0.5
        assert ks.iteration == 3


class TestKnowledgeStateToString:
    """Test KnowledgeState.to_string() formatting."""

    def test_empty_state_shows_defaults(self):
        ks = KnowledgeState(original_query="my query")
        result = ks.to_string()
        assert "Original Query: my query" in result
        assert "- Nothing yet" in result
        assert "- Nothing specific" in result
        assert "- No searches yet" in result
        assert "- None yet" in result
        assert "0.0%" in result

    def test_with_facts(self):
        ks = KnowledgeState(
            original_query="q", key_facts=["Earth is round", "Sky is blue"]
        )
        result = ks.to_string()
        assert "- Earth is round" in result
        assert "- Sky is blue" in result
        assert "Nothing yet" not in result

    def test_with_uncertainties(self):
        ks = KnowledgeState(original_query="q", uncertainties=["Is Mars red?"])
        result = ks.to_string()
        assert "- Is Mars red?" in result
        assert "Nothing specific" not in result

    def test_search_history_shows_last_three(self):
        history = [{"query": f"search {i}"} for i in range(5)]
        ks = KnowledgeState(original_query="q", search_history=history)
        result = ks.to_string()
        assert "5 searches" in result
        # Only last 3 should appear
        assert "search 2" in result
        assert "search 3" in result
        assert "search 4" in result
        # First two should NOT appear
        assert "Search 1: search 0" not in result
        assert "Search 2: search 1" not in result

    def test_candidate_answers_sorted_by_confidence(self):
        candidates = [
            {"answer": "low", "confidence": 0.3},
            {"answer": "high", "confidence": 0.9},
            {"answer": "mid", "confidence": 0.6},
        ]
        ks = KnowledgeState(original_query="q", candidate_answers=candidates)
        result = ks.to_string()
        assert "3 total" in result
        # High confidence should appear before low
        high_pos = result.index("high")
        low_pos = result.index("- low")
        assert high_pos < low_pos

    def test_confidence_formatting(self):
        ks = KnowledgeState(original_query="q", confidence=0.753)
        result = ks.to_string()
        assert "75.3%" in result


class TestAssessAnswerNoCandidates:
    """Test _assess_answer with no candidates."""

    def test_no_candidates_sets_zero(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = []
        strat.knowledge_state.confidence = 0.5  # pre-existing value
        strat._assess_answer()
        assert strat.knowledge_state.confidence == 0.0


class TestAssessAnswerSingleCandidate:
    """Test _assess_answer with a single candidate."""

    def test_single_candidate_no_extras(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.7}
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        # base=0.7, fact_boost=0, uncertainty_penalty=0, search_boost=0, candidate_penalty=0
        assert strat.knowledge_state.confidence == 0.7

    def test_single_candidate_with_facts_boost(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.6}
        ]
        strat.knowledge_state.key_facts = ["f1", "f2", "f3"]  # 3*0.05 = 0.15
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        assert strat.knowledge_state.confidence == pytest.approx(0.75)

    def test_fact_boost_capped_at_0_2(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.5}
        ]
        strat.knowledge_state.key_facts = [
            "f"
        ] * 10  # 10*0.05=0.5, capped at 0.2
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        # base=0.5 + fact_boost=0.2 = 0.7
        assert strat.knowledge_state.confidence == pytest.approx(0.7)

    def test_uncertainty_penalty(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.8}
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = ["u1", "u2"]  # 2*0.05=0.1
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        assert strat.knowledge_state.confidence == pytest.approx(0.7)

    def test_uncertainty_penalty_capped_at_0_2(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.8}
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = ["u"] * 20  # capped at 0.2
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        assert strat.knowledge_state.confidence == pytest.approx(0.6)

    def test_search_boost(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.5}
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = [
            {"query": "q"}
        ] * 3  # 3*0.02=0.06
        strat._assess_answer()
        assert strat.knowledge_state.confidence == pytest.approx(0.56)

    def test_search_boost_capped_at_0_1(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.5}
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = [
            {"query": "q"}
        ] * 20  # capped at 0.1
        strat._assess_answer()
        assert strat.knowledge_state.confidence == pytest.approx(0.6)


class TestAssessAnswerConfidenceSpread:
    """Test _assess_answer confidence spread logic with two candidates."""

    def test_close_candidates_reduce_confidence(self):
        """Two candidates within 0.1 spread triggers 0.8 multiplier on base."""
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.8},
            {"answer": "B", "confidence": 0.75},
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        # spread=0.05 < 0.1 => base = 0.8 * 0.8 = 0.64
        assert strat.knowledge_state.confidence == pytest.approx(0.64)

    def test_spread_exactly_0_1_triggers_reduction(self):
        """Spread of exactly 0.1 is NOT < 0.1, so no reduction."""
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.8},
            {"answer": "B", "confidence": 0.7},
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        # spread=0.1, NOT < 0.1, so base stays 0.8
        assert strat.knowledge_state.confidence == pytest.approx(0.8)

    def test_wide_spread_no_reduction(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.9},
            {"answer": "B", "confidence": 0.3},
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        assert strat.knowledge_state.confidence == pytest.approx(0.9)


class TestAssessAnswerCandidatePenalty:
    """Test candidate_penalty for > 3 candidates."""

    def test_three_candidates_no_penalty(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.9},
            {"answer": "B", "confidence": 0.5},
            {"answer": "C", "confidence": 0.3},
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        # spread = 0.9-0.5=0.4 > 0.1 => no reduction, base=0.9
        # no candidate_penalty (3 candidates, threshold > 3)
        assert strat.knowledge_state.confidence == pytest.approx(0.9)

    def test_four_candidates_small_penalty(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.8},
            {"answer": "B", "confidence": 0.3},
            {"answer": "C", "confidence": 0.2},
            {"answer": "D", "confidence": 0.1},
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        # base=0.8, spread=0.5 no reduction, candidate_penalty = (4-3)*0.05 = 0.05
        assert strat.knowledge_state.confidence == pytest.approx(0.75)

    def test_candidate_penalty_capped_at_0_15(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": f"ans{i}", "confidence": 0.9 - i * 0.05}
            for i in range(10)
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        # 10 candidates, spread = 0.9-0.85=0.05 < 0.1 => base=0.9*0.8=0.72
        # candidate_penalty = min((10-3)*0.05, 0.15) = min(0.35, 0.15) = 0.15
        assert strat.knowledge_state.confidence == pytest.approx(0.72 - 0.15)


class TestAssessAnswerCap:
    """Test the 0.95 confidence cap."""

    def test_cap_at_0_95(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.99}
        ]
        strat.knowledge_state.key_facts = ["f"] * 5  # +0.2 boost (capped)
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = [
            {"query": "q"}
        ] * 10  # +0.1 boost (capped)
        strat._assess_answer()
        # base=0.99 + 0.2 + 0.1 = 1.29, capped at 0.95
        assert strat.knowledge_state.confidence == 0.95

    def test_exactly_at_cap(self):
        """When all components sum to exactly 0.95."""
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.95}
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        assert strat.knowledge_state.confidence == 0.95


class TestAssessAnswerCombined:
    """Test _assess_answer with multiple factors combined."""

    def test_all_factors_combined(self):
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.7},
            {"answer": "B", "confidence": 0.3},
            {"answer": "C", "confidence": 0.2},
            {"answer": "D", "confidence": 0.1},
        ]
        strat.knowledge_state.key_facts = ["f1", "f2"]  # +0.10
        strat.knowledge_state.uncertainties = ["u1"]  # -0.05
        strat.knowledge_state.search_history = [{"query": "q"}] * 2  # +0.04
        strat._assess_answer()
        # base=0.7 (spread=0.4, no reduction)
        # fact_boost=0.10, search_boost=0.04, uncertainty_penalty=0.05
        # candidate_penalty=(4-3)*0.05=0.05
        # total = 0.7 + 0.10 + 0.04 - 0.05 - 0.05 = 0.74
        assert strat.knowledge_state.confidence == pytest.approx(0.74)

    def test_negative_result_possible(self):
        """If penalties exceed base + boosts, confidence can go negative."""
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.1},
            {
                "answer": "B",
                "confidence": 0.09,
            },  # spread=0.01 < 0.1 => base=0.1*0.8=0.08
            {"answer": "C", "confidence": 0.05},
            {"answer": "D", "confidence": 0.04},
            {"answer": "E", "confidence": 0.03},
            {"answer": "F", "confidence": 0.02},
        ]
        strat.knowledge_state.key_facts = []
        strat.knowledge_state.uncertainties = ["u"] * 4  # -0.2
        strat.knowledge_state.search_history = []
        strat._assess_answer()
        # base=0.08, candidate_penalty=min((6-3)*0.05,0.15)=0.15, uncertainty=0.2
        # total = 0.08 - 0.2 - 0.15 = -0.27
        assert strat.knowledge_state.confidence < 0

    def test_close_spread_with_boosts(self):
        """Close spread reduction combined with fact/search boosts."""
        strat = _make_strategy()
        strat.knowledge_state.candidate_answers = [
            {"answer": "A", "confidence": 0.9},
            {"answer": "B", "confidence": 0.88},
        ]
        strat.knowledge_state.key_facts = ["f"] * 4  # +0.2
        strat.knowledge_state.uncertainties = []
        strat.knowledge_state.search_history = [{"query": "q"}] * 5  # +0.1
        strat._assess_answer()
        # spread=0.02 < 0.1 => base = 0.9*0.8 = 0.72
        # total = 0.72 + 0.2 + 0.1 = 1.02, capped at 0.95
        assert strat.knowledge_state.confidence == 0.95
