"""
Pure-logic tests for IterativeReasoningStrategy._assess_answer
and KnowledgeState.to_string.

Tests the multi-factor confidence model (spread penalty, fact boost,
uncertainty penalty, search boost, candidate penalty, 0.95 cap) and
knowledge state formatting — no LLM or search calls.
"""

from unittest.mock import Mock

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


def _make_strategy(
    candidate_answers=None,
    key_facts=None,
    uncertainties=None,
    search_history=None,
):
    """Build a minimal Mock with a KnowledgeState."""
    s = Mock(spec=[])
    s.knowledge_state = KnowledgeState(
        original_query="test query",
        candidate_answers=candidate_answers or [],
        key_facts=key_facts or [],
        uncertainties=uncertainties or [],
        search_history=search_history or [],
    )
    return s


# ---------------------------------------------------------------------------
# KnowledgeState.to_string
# ---------------------------------------------------------------------------


class TestKnowledgeStateToString:
    """Verify knowledge state formatting."""

    def test_empty_state(self):
        """Empty state shows 'Nothing yet' for facts and uncertainties."""
        ks = KnowledgeState(original_query="What is X?")
        result = ks.to_string()
        assert "What is X?" in result
        assert "Nothing yet" in result
        assert "No searches yet" in result
        assert "None yet" in result

    def test_with_facts(self):
        """Key facts are listed."""
        ks = KnowledgeState(
            original_query="Q",
            key_facts=["Fact A", "Fact B"],
        )
        result = ks.to_string()
        assert "Fact A" in result
        assert "Fact B" in result

    def test_with_candidates(self):
        """Candidates are sorted by confidence descending."""
        ks = KnowledgeState(
            original_query="Q",
            candidate_answers=[
                {"answer": "Low", "confidence": 0.3},
                {"answer": "High", "confidence": 0.9},
            ],
        )
        result = ks.to_string()
        # High should appear before Low
        assert result.index("High") < result.index("Low")

    def test_search_history_shows_last_three(self):
        """Only last 3 searches are shown."""
        ks = KnowledgeState(
            original_query="Q",
            search_history=[{"query": f"search {i}"} for i in range(5)],
        )
        result = ks.to_string()
        assert "search 2" in result
        assert "search 4" in result
        # search 0 and 1 should not be shown (only last 3)
        assert "search 0" not in result

    def test_confidence_formatted(self):
        """Current confidence is formatted as percentage."""
        ks = KnowledgeState(original_query="Q", confidence=0.75)
        result = ks.to_string()
        assert "75.0%" in result


# ---------------------------------------------------------------------------
# _assess_answer
# ---------------------------------------------------------------------------


class TestAssessAnswer:
    """Verify multi-factor confidence assessment."""

    def _assess(self, **kwargs):
        s = _make_strategy(**kwargs)
        IterativeReasoningStrategy._assess_answer(s)
        return s.knowledge_state.confidence

    def test_no_candidates(self):
        """No candidates -> confidence 0.0."""
        assert self._assess(candidate_answers=[]) == 0.0

    def test_single_candidate_base(self):
        """Single candidate: confidence = candidate confidence + boosts."""
        conf = self._assess(
            candidate_answers=[{"answer": "A", "confidence": 0.7}],
        )
        # base 0.7 + fact_boost 0 + search_boost 0 - uncertainty 0 - candidate_penalty 0
        assert abs(conf - 0.7) < 1e-9

    def test_close_spread_penalty(self):
        """Two candidates within 0.1 spread -> 20% reduction of base."""
        conf = self._assess(
            candidate_answers=[
                {"answer": "A", "confidence": 0.8},
                {"answer": "B", "confidence": 0.75},
            ],
        )
        # spread = 0.05 < 0.1 -> base = 0.8 * 0.8 = 0.64
        assert abs(conf - 0.64) < 1e-9

    def test_wide_spread_no_penalty(self):
        """Two candidates with spread >= 0.1 -> no reduction."""
        conf = self._assess(
            candidate_answers=[
                {"answer": "A", "confidence": 0.8},
                {"answer": "B", "confidence": 0.5},
            ],
        )
        # spread = 0.3 >= 0.1 -> base stays 0.8
        assert abs(conf - 0.8) < 1e-9

    def test_fact_boost(self):
        """Key facts add 0.05 each, capped at 0.2."""
        conf = self._assess(
            candidate_answers=[{"answer": "A", "confidence": 0.5}],
            key_facts=["f1", "f2", "f3"],
        )
        # base 0.5 + fact_boost min(3*0.05, 0.2) = 0.15
        assert abs(conf - 0.65) < 1e-9

    def test_fact_boost_capped(self):
        """Fact boost capped at 0.2."""
        conf = self._assess(
            candidate_answers=[{"answer": "A", "confidence": 0.5}],
            key_facts=[f"f{i}" for i in range(10)],
        )
        # base 0.5 + fact_boost 0.2
        assert abs(conf - 0.7) < 1e-9

    def test_uncertainty_penalty(self):
        """Uncertainties subtract 0.05 each, capped at 0.2."""
        conf = self._assess(
            candidate_answers=[{"answer": "A", "confidence": 0.8}],
            uncertainties=["u1", "u2"],
        )
        # base 0.8 - uncertainty min(2*0.05, 0.2) = 0.1
        assert abs(conf - 0.7) < 1e-9

    def test_search_boost(self):
        """Search history adds 0.02 each, capped at 0.1."""
        conf = self._assess(
            candidate_answers=[{"answer": "A", "confidence": 0.5}],
            search_history=[{"query": f"q{i}"} for i in range(3)],
        )
        # base 0.5 + search_boost min(3*0.02, 0.1) = 0.06
        assert abs(conf - 0.56) < 1e-9

    def test_candidate_penalty(self):
        """More than 3 candidates incur penalty of 0.05 each, capped at 0.15."""
        conf = self._assess(
            candidate_answers=[
                {"answer": f"A{i}", "confidence": 0.8 - i * 0.15}
                for i in range(5)
            ],
        )
        # 5 candidates -> penalty = min((5-3)*0.05, 0.15) = 0.1
        # spread: 0.8 vs 0.65 = 0.15 >= 0.1 -> no spread penalty
        # base 0.8 - 0.1 = 0.7
        assert abs(conf - 0.7) < 1e-9

    def test_capped_at_095(self):
        """Confidence never exceeds 0.95."""
        conf = self._assess(
            candidate_answers=[{"answer": "A", "confidence": 0.95}],
            key_facts=[f"f{i}" for i in range(10)],
            search_history=[{"query": f"q{i}"} for i in range(10)],
        )
        assert conf == 0.95
