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

Tests cover:
- StepType enum values
- StepResult dataclass fields and defaults
- _calculate_confidence() pure math logic
- _execute_step() routing logic
"""

from unittest.mock import MagicMock, patch

import pytest

from local_deep_research.advanced_search_system.strategies.adaptive_decomposition_strategy import (
    AdaptiveDecompositionStrategy,
    StepResult,
    StepType,
)


# ---------------------------------------------------------------------------
# StepType enum
# ---------------------------------------------------------------------------
class TestStepTypeEnum:
    """Tests for the StepType enum values."""

    def test_constraint_extraction_value(self):
        assert StepType.CONSTRAINT_EXTRACTION.value == "constraint_extraction"

    def test_initial_search_value(self):
        assert StepType.INITIAL_SEARCH.value == "initial_search"

    def test_verification_value(self):
        assert StepType.VERIFICATION.value == "verification"

    def test_refinement_value(self):
        assert StepType.REFINEMENT.value == "refinement"

    def test_synthesis_value(self):
        assert StepType.SYNTHESIS.value == "synthesis"

    def test_enum_has_exactly_five_members(self):
        assert len(StepType) == 5


# ---------------------------------------------------------------------------
# StepResult dataclass
# ---------------------------------------------------------------------------
class TestStepResultDataclass:
    """Tests for the StepResult dataclass fields and defaults."""

    def test_required_fields(self):
        result = StepResult(
            step_type=StepType.SYNTHESIS,
            description="test desc",
            findings={"key": "val"},
        )
        assert result.step_type == StepType.SYNTHESIS
        assert result.description == "test desc"
        assert result.findings == {"key": "val"}

    def test_default_next_action_is_none(self):
        result = StepResult(
            step_type=StepType.VERIFICATION,
            description="",
            findings={},
        )
        assert result.next_action is None

    def test_default_confidence_is_zero(self):
        result = StepResult(
            step_type=StepType.REFINEMENT,
            description="",
            findings={},
        )
        assert result.confidence == 0.0

    def test_custom_next_action(self):
        result = StepResult(
            step_type=StepType.INITIAL_SEARCH,
            description="d",
            findings={},
            next_action="refine",
        )
        assert result.next_action == "refine"

    def test_custom_confidence(self):
        result = StepResult(
            step_type=StepType.INITIAL_SEARCH,
            description="d",
            findings={},
            confidence=0.75,
        )
        assert result.confidence == 0.75


# ---------------------------------------------------------------------------
# Helpers for strategy tests
# ---------------------------------------------------------------------------
def _make_strategy() -> AdaptiveDecompositionStrategy:
    """Create a strategy instance with __init__ bypassed."""
    with patch.object(
        AdaptiveDecompositionStrategy, "__init__", lambda self, *a, **kw: None
    ):
        strategy = AdaptiveDecompositionStrategy()
    # Provide the minimal state that _calculate_confidence needs
    strategy.working_knowledge = {
        "constraints": [],
        "candidates": [],
        "verified_facts": [],
        "uncertainties": [],
    }
    return strategy


# ---------------------------------------------------------------------------
# _calculate_confidence
# ---------------------------------------------------------------------------
class TestCalculateConfidence:
    """Tests for the _calculate_confidence pure math method."""

    def test_empty_constraints_returns_zero(self):
        strategy = _make_strategy()
        strategy.working_knowledge["constraints"] = []
        assert strategy._calculate_confidence() == 0.0

    def test_no_verified_facts_returns_zero(self):
        strategy = _make_strategy()
        strategy.working_knowledge["constraints"] = ["c1", "c2"]
        strategy.working_knowledge["verified_facts"] = []
        strategy.working_knowledge["candidates"] = []
        assert strategy._calculate_confidence() == 0.0

    def test_ratio_half(self):
        strategy = _make_strategy()
        strategy.working_knowledge["constraints"] = ["c1", "c2"]
        strategy.working_knowledge["verified_facts"] = ["f1"]
        strategy.working_knowledge["candidates"] = []
        assert strategy._calculate_confidence() == pytest.approx(0.5)

    def test_ratio_full_no_candidates(self):
        strategy = _make_strategy()
        strategy.working_knowledge["constraints"] = ["c1"]
        strategy.working_knowledge["verified_facts"] = ["f1"]
        strategy.working_knowledge["candidates"] = []
        # 1/1 = 1.0, no boost, capped at 0.95
        assert strategy._calculate_confidence() == pytest.approx(0.95)

    def test_candidate_boost_adds_point_one(self):
        strategy = _make_strategy()
        strategy.working_knowledge["constraints"] = ["c1", "c2"]
        strategy.working_knowledge["verified_facts"] = ["f1"]
        strategy.working_knowledge["candidates"] = ["loc1"]
        # 1/2 + 0.1 = 0.6
        assert strategy._calculate_confidence() == pytest.approx(0.6)

    def test_candidate_boost_with_full_ratio_caps_at_095(self):
        strategy = _make_strategy()
        strategy.working_knowledge["constraints"] = ["c1"]
        strategy.working_knowledge["verified_facts"] = ["f1"]
        strategy.working_knowledge["candidates"] = ["loc1"]
        # 1/1 + 0.1 = 1.1, capped at 0.95
        assert strategy._calculate_confidence() == pytest.approx(0.95)

    def test_verified_exceeds_constraints_caps_at_095(self):
        strategy = _make_strategy()
        strategy.working_knowledge["constraints"] = ["c1"]
        strategy.working_knowledge["verified_facts"] = ["f1", "f2", "f3"]
        strategy.working_knowledge["candidates"] = []
        # 3/1 = 3.0, capped at 0.95
        assert strategy._calculate_confidence() == pytest.approx(0.95)

    def test_many_constraints_low_verified(self):
        strategy = _make_strategy()
        strategy.working_knowledge["constraints"] = [
            "c1",
            "c2",
            "c3",
            "c4",
            "c5",
        ]
        strategy.working_knowledge["verified_facts"] = ["f1"]
        strategy.working_knowledge["candidates"] = []
        # 1/5 = 0.2
        assert strategy._calculate_confidence() == pytest.approx(0.2)

    def test_many_constraints_low_verified_with_candidates(self):
        strategy = _make_strategy()
        strategy.working_knowledge["constraints"] = [
            "c1",
            "c2",
            "c3",
            "c4",
            "c5",
        ]
        strategy.working_knowledge["verified_facts"] = ["f1"]
        strategy.working_knowledge["candidates"] = ["loc1"]
        # 1/5 + 0.1 = 0.3
        assert strategy._calculate_confidence() == pytest.approx(0.3)

    def test_empty_candidates_list_no_boost(self):
        """An empty list is falsy, so no +0.1 boost."""
        strategy = _make_strategy()
        strategy.working_knowledge["constraints"] = ["c1", "c2"]
        strategy.working_knowledge["verified_facts"] = ["f1"]
        strategy.working_knowledge["candidates"] = []
        assert strategy._calculate_confidence() == pytest.approx(0.5)


# ---------------------------------------------------------------------------
# _execute_step routing
# ---------------------------------------------------------------------------
class TestExecuteStepRouting:
    """Tests that _execute_step dispatches to the correct method."""

    def _strategy_with_mocks(self):
        strategy = _make_strategy()
        strategy._extract_constraints = MagicMock(
            return_value="constraints_result"
        )
        strategy._perform_initial_search = MagicMock(
            return_value="search_result"
        )
        strategy._verify_candidates = MagicMock(return_value="verify_result")
        strategy._refine_search = MagicMock(return_value="refine_result")
        strategy._synthesize_current_knowledge = MagicMock(
            return_value="synthesis_result"
        )
        return strategy

    def test_routes_constraint_extraction(self):
        strategy = self._strategy_with_mocks()
        step = StepResult(
            step_type=StepType.CONSTRAINT_EXTRACTION,
            description="desc",
            findings={},
        )
        result = strategy._execute_step(step, "query")
        assert result == "constraints_result"
        strategy._extract_constraints.assert_called_once_with("query")

    def test_routes_initial_search(self):
        strategy = self._strategy_with_mocks()
        step = StepResult(
            step_type=StepType.INITIAL_SEARCH,
            description="search desc",
            findings={},
        )
        result = strategy._execute_step(step, "query")
        assert result == "search_result"
        strategy._perform_initial_search.assert_called_once_with("search desc")

    def test_routes_verification(self):
        strategy = self._strategy_with_mocks()
        step = StepResult(
            step_type=StepType.VERIFICATION,
            description="verify desc",
            findings={},
        )
        result = strategy._execute_step(step, "query")
        assert result == "verify_result"
        strategy._verify_candidates.assert_called_once_with("verify desc")

    def test_routes_refinement(self):
        strategy = self._strategy_with_mocks()
        step = StepResult(
            step_type=StepType.REFINEMENT,
            description="refine desc",
            findings={},
        )
        result = strategy._execute_step(step, "query")
        assert result == "refine_result"
        strategy._refine_search.assert_called_once_with("refine desc")

    def test_routes_synthesis(self):
        strategy = self._strategy_with_mocks()
        step = StepResult(
            step_type=StepType.SYNTHESIS,
            description="synth desc",
            findings={},
        )
        result = strategy._execute_step(step, "query")
        assert result == "synthesis_result"
        strategy._synthesize_current_knowledge.assert_called_once_with("query")
