"""
Tests for DualConfidenceStrategy.

Tests cover:
- Initialization and inheritance
- Dual confidence scoring
- Evidence analysis
- Score extraction
- Error handling
"""

from unittest.mock import Mock, patch


class TestConstraintEvidence:
    """Tests for ConstraintEvidence dataclass."""

    def test_create_constraint_evidence(self):
        """Create constraint evidence with all fields."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            ConstraintEvidence,
        )

        evidence = ConstraintEvidence(
            positive_confidence=0.8,
            negative_confidence=0.1,
            uncertainty=0.1,
            evidence_text="Test evidence text",
            source="test_source",
        )

        assert evidence.positive_confidence == 0.8
        assert evidence.negative_confidence == 0.1
        assert evidence.uncertainty == 0.1
        assert evidence.evidence_text == "Test evidence text"
        assert evidence.source == "test_source"


class TestDualConfidenceStrategyInit:
    """Tests for DualConfidenceStrategy initialization."""

    def test_init_inherits_from_smart_query(self):
        """Initialize inherits from SmartQueryStrategy."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )
        from local_deep_research.advanced_search_system.strategies.smart_query_strategy import (
            SmartQueryStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        assert isinstance(strategy, SmartQueryStrategy)

    def test_init_default_params(self):
        """Initialize with default parameters."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        assert strategy.uncertainty_penalty == 0.2
        assert strategy.negative_weight == 0.5

    def test_init_custom_params(self):
        """Initialize with custom parameters."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
            uncertainty_penalty=0.3,
            negative_weight=0.7,
        )

        assert strategy.uncertainty_penalty == 0.3
        assert strategy.negative_weight == 0.7


class TestEvaluateEvidence:
    """Tests for _evaluate_evidence method."""

    def test_evaluate_evidence_empty_list(self):
        """Evaluate evidence handles empty evidence list."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        constraint = Constraint(
            id="1",
            type=ConstraintType.PROPERTY,
            value="test",
            description="Test",
            weight=0.5,
        )

        score = strategy._evaluate_evidence([], constraint)

        # No evidence means high uncertainty
        assert score == 0.5 - strategy.uncertainty_penalty

    def test_evaluate_evidence_with_list(self):
        """Evaluate evidence calculates score from evidence list."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_search = Mock()
        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="POSITIVE: 0.8\nNEGATIVE: 0.1\nUNCERTAINTY: 0.1"
        )

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        constraint = Constraint(
            id="1",
            type=ConstraintType.PROPERTY,
            value="test",
            description="Test",
            weight=0.5,
        )

        evidence_list = [{"text": "Test evidence text", "source": "search"}]

        score = strategy._evaluate_evidence(evidence_list, constraint)

        assert 0 <= score <= 1


class TestAnalyzeEvidenceDualConfidence:
    """Tests for _analyze_evidence_dual_confidence method."""

    def test_analyze_evidence_parses_scores(self):
        """Analyze evidence parses LLM response scores."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_search = Mock()
        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="POSITIVE: 0.7\nNEGATIVE: 0.2\nUNCERTAINTY: 0.1"
        )

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        constraint = Constraint(
            id="1",
            type=ConstraintType.PROPERTY,
            value="test",
            description="Test",
            weight=0.5,
        )

        evidence = {"text": "Evidence text", "source": "search"}

        result = strategy._analyze_evidence_dual_confidence(
            evidence, constraint
        )

        assert hasattr(result, "positive_confidence")
        assert hasattr(result, "negative_confidence")
        assert hasattr(result, "uncertainty")

    def test_analyze_evidence_normalizes_scores(self):
        """Analyze evidence normalizes scores to sum to 1."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_search = Mock()
        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="POSITIVE: 0.5\nNEGATIVE: 0.5\nUNCERTAINTY: 0.5"
        )

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        constraint = Constraint(
            id="1",
            type=ConstraintType.PROPERTY,
            value="test",
            description="Test",
            weight=0.5,
        )

        evidence = {"text": "Evidence text", "source": "search"}

        result = strategy._analyze_evidence_dual_confidence(
            evidence, constraint
        )

        # Scores should be normalized to sum to approximately 1
        total = (
            result.positive_confidence
            + result.negative_confidence
            + result.uncertainty
        )
        assert 0.99 <= total <= 1.01

    def test_analyze_evidence_handles_error(self):
        """Analyze evidence handles LLM errors gracefully."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_search = Mock()
        mock_model = Mock()
        mock_model.invoke.side_effect = Exception("LLM Error")

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        constraint = Constraint(
            id="1",
            type=ConstraintType.PROPERTY,
            value="test",
            description="Test",
            weight=0.5,
        )

        evidence = {"text": "Evidence text", "source": "search"}

        result = strategy._analyze_evidence_dual_confidence(
            evidence, constraint
        )

        # Should default to high uncertainty
        assert result.uncertainty == 0.8


class TestExtractScore:
    """Tests for _extract_score method."""

    def test_extract_score_finds_score(self):
        """Extract score finds score in text."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        text = "POSITIVE: 0.85"
        score = strategy._extract_score(text, "POSITIVE")

        assert score == 0.85

    def test_extract_score_finds_bracketed(self):
        """Extract score finds bracketed score."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        text = "POSITIVE: [0.75]"
        score = strategy._extract_score(text, "POSITIVE")

        assert score == 0.75

    def test_extract_score_not_found(self):
        """Extract score returns default when not found."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        text = "No score here"
        score = strategy._extract_score(text, "POSITIVE")

        assert score == 0.1  # Default low score

    def test_extract_score_case_insensitive(self):
        """Extract score is case insensitive."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        text = "positive: 0.65"
        score = strategy._extract_score(text, "POSITIVE")

        assert score == 0.65

    def test_extract_score_returns_default_for_non_numeric_match(self):
        """Extract score returns default when regex matches but float() fails (PR #2020).

        PR #2020 changed bare `except:` to `except ValueError:` for float
        conversion errors in _extract_score.
        """
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        # No numeric match for POSITIVE label
        text = "POSITIVE: invalid"
        score = strategy._extract_score(text, "POSITIVE")

        assert score == 0.1  # Default low score

    def test_extract_score_returns_default_for_empty_input(self):
        """Extract score returns default for empty input."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        score = strategy._extract_score("", "POSITIVE")
        assert score == 0.1


class TestGatherEvidenceForConstraint:
    """Tests for _gather_evidence_for_constraint method."""

    def test_gather_evidence_creates_queries(self):
        """Gather evidence creates targeted queries."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )
        from local_deep_research.advanced_search_system.candidates.base_candidate import (
            Candidate,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_search = Mock()
        mock_search.run.return_value = [{"snippet": "Test result"}]
        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Test response")

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )
        strategy.searched_queries = set()
        strategy.use_direct_search = True

        candidate = Candidate(name="Test Entity")
        constraint = Constraint(
            id="1",
            type=ConstraintType.PROPERTY,
            value="has feature",
            description="Test constraint",
            weight=0.5,
        )

        # Mock _execute_search
        with patch.object(
            strategy,
            "_execute_search",
            return_value={"current_knowledge": "Test content"},
        ):
            evidence = strategy._gather_evidence_for_constraint(
                candidate, constraint
            )

        assert isinstance(evidence, list)

    def test_gather_evidence_includes_negative_queries(self):
        """Gather evidence includes negative queries for properties."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )
        from local_deep_research.advanced_search_system.candidates.base_candidate import (
            Candidate,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )
        strategy.searched_queries = set()

        candidate = Candidate(name="Test Entity")
        constraint = Constraint(
            id="1",
            type=ConstraintType.PROPERTY,
            value="feature",
            description="Test property",
            weight=0.5,
        )

        # The method should build queries including negative ones
        # We can check this by looking at what queries would be built
        # For property constraints, it should include NOT queries
        # Just verify the method exists and runs without error
        with patch.object(
            strategy,
            "_execute_search",
            return_value={"current_knowledge": ""},
        ):
            evidence = strategy._gather_evidence_for_constraint(
                candidate, constraint
            )

        assert isinstance(evidence, list)


class TestScoreCalculation:
    """Tests for score calculation logic."""

    def test_score_high_positive_low_negative(self):
        """High positive and low negative gives high score."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
            ConstraintEvidence,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        # Manually calculate expected score
        # score = avg_positive - (avg_negative * negative_weight) - (avg_uncertainty * uncertainty_penalty)
        # With positive=0.8, negative=0.1, uncertainty=0.1
        # score = 0.8 - (0.1 * 0.5) - (0.1 * 0.2) = 0.8 - 0.05 - 0.02 = 0.73

        constraint = Constraint(
            id="1",
            type=ConstraintType.PROPERTY,
            value="test",
            description="Test",
            weight=0.5,
        )

        with patch.object(
            strategy,
            "_analyze_evidence_dual_confidence",
            return_value=ConstraintEvidence(
                positive_confidence=0.8,
                negative_confidence=0.1,
                uncertainty=0.1,
                evidence_text="test",
                source="test",
            ),
        ):
            evidence_list = [{"text": "test", "source": "test"}]
            score = strategy._evaluate_evidence(evidence_list, constraint)

        assert score > 0.5  # Should be relatively high

    def test_score_low_positive_high_negative(self):
        """Low positive and high negative gives low score."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
            ConstraintEvidence,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        constraint = Constraint(
            id="1",
            type=ConstraintType.PROPERTY,
            value="test",
            description="Test",
            weight=0.5,
        )

        with patch.object(
            strategy,
            "_analyze_evidence_dual_confidence",
            return_value=ConstraintEvidence(
                positive_confidence=0.1,
                negative_confidence=0.8,
                uncertainty=0.1,
                evidence_text="test",
                source="test",
            ),
        ):
            evidence_list = [{"text": "test", "source": "test"}]
            score = strategy._evaluate_evidence(evidence_list, constraint)

        assert score < 0.5  # Should be relatively low


class TestErrorHandling:
    """Tests for error handling."""

    def test_analyze_evidence_invalid_response(self):
        """Analyze evidence handles invalid LLM response."""
        from local_deep_research.advanced_search_system.strategies.dual_confidence_strategy import (
            DualConfidenceStrategy,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_search = Mock()
        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Invalid response with no scores"
        )

        strategy = DualConfidenceStrategy(
            model=mock_model,
            search=mock_search,
            all_links_of_system=[],
        )

        constraint = Constraint(
            id="1",
            type=ConstraintType.PROPERTY,
            value="test",
            description="Test",
            weight=0.5,
        )

        evidence = {"text": "Evidence text", "source": "search"}

        result = strategy._analyze_evidence_dual_confidence(
            evidence, constraint
        )

        # Should still return valid ConstraintEvidence
        assert hasattr(result, "positive_confidence")
        assert hasattr(result, "negative_confidence")
        assert hasattr(result, "uncertainty")
