"""High-value tests for llm_driven_modular_strategy.py pure logic."""

import unittest
from unittest.mock import MagicMock

from local_deep_research.advanced_search_system.strategies.llm_driven_modular_strategy import (
    CandidateConfidence,
    EarlyRejectionManager,
    LLMConstraintProcessor,
)


class TestCandidateConfidence(unittest.TestCase):
    def test_required_fields(self):
        cc = CandidateConfidence(
            candidate="test", positive_confidence=0.8, negative_confidence=0.2
        )
        assert cc.candidate == "test"
        assert cc.positive_confidence == 0.8
        assert cc.negative_confidence == 0.2

    def test_default_should_continue(self):
        cc = CandidateConfidence(
            candidate="x", positive_confidence=0.5, negative_confidence=0.5
        )
        assert cc.should_continue is True

    def test_default_rejection_reason(self):
        cc = CandidateConfidence(
            candidate="x", positive_confidence=0.5, negative_confidence=0.5
        )
        assert cc.rejection_reason is None

    def test_override_defaults(self):
        cc = CandidateConfidence(
            candidate="x",
            positive_confidence=0.1,
            negative_confidence=0.9,
            should_continue=False,
            rejection_reason="too negative",
        )
        assert cc.should_continue is False
        assert cc.rejection_reason == "too negative"


class TestLLMConstraintProcessorParsing(unittest.TestCase):
    def setUp(self):
        self.processor = LLMConstraintProcessor(model=MagicMock())

    def test_parse_decomposition_valid_json(self):
        content = '{"c1": {"atomic_elements": ["a"], "variations": ["b"], "granular_specifics": ["c"]}}'
        result = self.processor._parse_decomposition(content)
        assert "c1" in result
        assert result["c1"]["atomic_elements"] == ["a"]

    def test_parse_decomposition_invalid_json_fallback(self):
        result = self.processor._parse_decomposition("not json at all")
        assert "time_constraint" in result
        assert "atomic_elements" in result["time_constraint"]

    def test_parse_decomposition_with_code_fence(self):
        content = '```json\n{"key": {"atomic_elements": ["x"]}}\n```'
        result = self.processor._parse_decomposition(content)
        assert "key" in result

    def test_parse_combinations_valid_list(self):
        content = '["query1", "query2", "query3"]'
        result = self.processor._parse_combinations(content)
        assert result == ["query1", "query2", "query3"]

    def test_parse_combinations_invalid_fallback(self):
        result = self.processor._parse_combinations("no json here")
        assert isinstance(result, list)
        assert len(result) > 0
        assert all(isinstance(q, str) for q in result)

    def test_parse_creative_searches_valid(self):
        content = '["creative1", "creative2"]'
        result = self.processor._parse_creative_searches(content)
        assert result == ["creative1", "creative2"]

    def test_parse_creative_searches_fallback(self):
        result = self.processor._parse_creative_searches("bad")
        assert isinstance(result, list)
        assert len(result) > 0
        assert all(isinstance(q, str) for q in result)

    def test_parse_optimized_searches_valid(self):
        content = '{"high_priority": ["q1"], "fallback_broad": ["q2"]}'
        result = self.processor._parse_optimized_searches(content)
        assert "high_priority" in result

    def test_parse_optimized_searches_fallback(self):
        result = self.processor._parse_optimized_searches("invalid")
        assert isinstance(result, dict)
        assert "high_priority" in result
        assert "systematic_granular" in result
        assert "creative_angles" in result
        assert "contextual_searches" in result
        assert "fallback_broad" in result


class TestEarlyRejectionManagerInit(unittest.TestCase):
    def test_default_thresholds(self):
        mgr = EarlyRejectionManager(model=MagicMock())
        assert mgr.positive_threshold == 0.6
        assert mgr.negative_threshold == 0.3

    def test_custom_thresholds(self):
        mgr = EarlyRejectionManager(
            model=MagicMock(), positive_threshold=0.8, negative_threshold=0.1
        )
        assert mgr.positive_threshold == 0.8
        assert mgr.negative_threshold == 0.1

    def test_rejected_candidates_empty(self):
        mgr = EarlyRejectionManager(model=MagicMock())
        assert mgr.rejected_candidates == set()


class TestShouldRejectEarly(unittest.TestCase):
    def setUp(self):
        self.mgr = EarlyRejectionManager(model=MagicMock())

    def test_high_negative_rejects(self):
        reject, reason = self.mgr.should_reject_early(
            {"positive_confidence": 0.5, "negative_confidence": 0.8}
        )
        assert reject is True

    def test_very_low_positive_rejects(self):
        reject, reason = self.mgr.should_reject_early(
            {"positive_confidence": 0.05, "negative_confidence": 0.3}
        )
        assert reject is True

    def test_boundary_negative_0_7_no_reject(self):
        reject, reason = self.mgr.should_reject_early(
            {"positive_confidence": 0.5, "negative_confidence": 0.7}
        )
        assert reject is False

    def test_boundary_positive_0_1_no_reject(self):
        reject, reason = self.mgr.should_reject_early(
            {"positive_confidence": 0.1, "negative_confidence": 0.3}
        )
        assert reject is False

    def test_moderate_no_reject(self):
        reject, reason = self.mgr.should_reject_early(
            {"positive_confidence": 0.5, "negative_confidence": 0.5}
        )
        assert reject is False

    def test_missing_keys_use_defaults(self):
        reject, reason = self.mgr.should_reject_early({})
        # defaults: positive=0.5, negative=0.3 -> no reject
        assert reject is False


class TestShouldContinueSearch(unittest.TestCase):
    def setUp(self):
        self.mgr = EarlyRejectionManager(model=MagicMock())

    def test_enough_high_confidence_stops(self):
        cont, reason = self.mgr.should_continue_search(
            all_candidates=list(range(10)), high_confidence_count=5
        )
        assert cont is False

    def test_4_high_confidence_continues(self):
        cont, reason = self.mgr.should_continue_search(
            all_candidates=list(range(10)), high_confidence_count=4
        )
        assert cont is True

    def test_many_low_quality_stops(self):
        cont, reason = self.mgr.should_continue_search(
            all_candidates=list(range(51)), high_confidence_count=0
        )
        assert cont is False

    def test_50_candidates_0_quality_continues(self):
        cont, reason = self.mgr.should_continue_search(
            all_candidates=list(range(50)), high_confidence_count=0
        )
        assert cont is True

    def test_normal_continues(self):
        cont, reason = self.mgr.should_continue_search(
            all_candidates=list(range(20)), high_confidence_count=2
        )
        assert cont is True


class TestParseConfidence(unittest.TestCase):
    def setUp(self):
        self.mgr = EarlyRejectionManager(model=MagicMock())

    def test_valid_json(self):
        content = '{"positive_confidence": 0.8, "negative_confidence": 0.1, "reasoning": "good match"}'
        result = self.mgr._parse_confidence(content)
        assert result["positive_confidence"] == 0.8
        assert result["negative_confidence"] == 0.1

    def test_invalid_json_fallback(self):
        result = self.mgr._parse_confidence("not json")
        assert result["positive_confidence"] == 0.5
        assert result["negative_confidence"] == 0.3
        assert result["reasoning"] == "parse_error"


if __name__ == "__main__":
    unittest.main()
