"""
Pure-logic tests for Candidate, Constraint, and EvidenceType.

Tests scoring, unverified-constraint filtering, weak-evidence detection,
search-term generation, criticality checks, and base confidence mapping,
requiring no LLM or search calls.
"""

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,
)
from local_deep_research.advanced_search_system.evidence.base_evidence import (
    Evidence,
    EvidenceType,
)


def _constraint(id, ctype=ConstraintType.PROPERTY, weight=1.0, value="v"):
    return Constraint(
        id=id, type=ctype, description="", value=value, weight=weight
    )


def _evidence(confidence=0.8):
    return Evidence(
        claim="test",
        type=EvidenceType.DIRECT_STATEMENT,
        source="test",
        confidence=confidence,
    )


# ---------------------------------------------------------------------------
# Candidate.calculate_score
# ---------------------------------------------------------------------------


class TestCandidateCalculateScore:
    """Verify weighted-average scoring of candidate evidence."""

    def test_no_constraints(self):
        """No constraints -> 0.0."""
        c = Candidate(name="X")
        assert c.calculate_score([]) == 0.0

    def test_single_constraint_with_evidence(self):
        """One constraint, full evidence -> confidence * weight / weight."""
        c = Candidate(name="X")
        con = _constraint("c1", weight=1.0)
        c.add_evidence("c1", _evidence(confidence=0.9))
        score = c.calculate_score([con])
        assert abs(score - 0.9) < 1e-9

    def test_mixed_weights(self):
        """Two constraints with different weights."""
        c = Candidate(name="X")
        c1 = _constraint("c1", weight=2.0)
        c2 = _constraint("c2", weight=1.0)
        c.add_evidence("c1", _evidence(confidence=0.8))
        c.add_evidence("c2", _evidence(confidence=0.5))
        # (0.8*2.0 + 0.5*1.0) / (2.0+1.0) = 2.1/3.0 = 0.7
        score = c.calculate_score([c1, c2])
        assert abs(score - 0.7) < 1e-9

    def test_missing_evidence(self):
        """Constraint without evidence contributes 0 to numerator."""
        c = Candidate(name="X")
        c1 = _constraint("c1", weight=1.0)
        c2 = _constraint("c2", weight=1.0)
        c.add_evidence("c1", _evidence(confidence=1.0))
        # c2 has no evidence -> 0 contribution
        score = c.calculate_score([c1, c2])
        assert abs(score - 0.5) < 1e-9

    def test_updates_score_attribute(self):
        """calculate_score should also set self.score."""
        c = Candidate(name="X")
        con = _constraint("c1")
        c.add_evidence("c1", _evidence(confidence=0.7))
        c.calculate_score([con])
        assert abs(c.score - 0.7) < 1e-9


# ---------------------------------------------------------------------------
# Candidate.get_unverified_constraints
# ---------------------------------------------------------------------------


class TestGetUnverifiedConstraints:
    """Verify filtering of constraints without evidence."""

    def test_all_verified(self):
        """All constraints have evidence -> empty list."""
        c = Candidate(name="X")
        con = _constraint("c1")
        c.add_evidence("c1", _evidence())
        assert c.get_unverified_constraints([con]) == []

    def test_none_verified(self):
        """No evidence at all -> all constraints returned."""
        c = Candidate(name="X")
        cons = [_constraint("c1"), _constraint("c2")]
        result = c.get_unverified_constraints(cons)
        assert len(result) == 2

    def test_partial(self):
        """Some constraints verified, some not."""
        c = Candidate(name="X")
        c.add_evidence("c1", _evidence())
        cons = [_constraint("c1"), _constraint("c2"), _constraint("c3")]
        result = c.get_unverified_constraints(cons)
        assert [r.id for r in result] == ["c2", "c3"]


# ---------------------------------------------------------------------------
# Candidate.get_weak_evidence
# ---------------------------------------------------------------------------


class TestGetWeakEvidence:
    """Verify filtering of low-confidence evidence."""

    def test_default_threshold(self):
        """Default threshold 0.5: evidence at 0.3 is weak."""
        c = Candidate(name="X")
        c.add_evidence("c1", _evidence(confidence=0.3))
        c.add_evidence("c2", _evidence(confidence=0.8))
        assert c.get_weak_evidence() == ["c1"]

    def test_custom_threshold(self):
        """Custom threshold 0.9: evidence at 0.8 is now weak."""
        c = Candidate(name="X")
        c.add_evidence("c1", _evidence(confidence=0.8))
        assert c.get_weak_evidence(threshold=0.9) == ["c1"]

    def test_no_weak(self):
        """All evidence above threshold -> empty list."""
        c = Candidate(name="X")
        c.add_evidence("c1", _evidence(confidence=0.9))
        assert c.get_weak_evidence() == []

    def test_all_weak(self):
        """All evidence below threshold."""
        c = Candidate(name="X")
        c.add_evidence("c1", _evidence(confidence=0.1))
        c.add_evidence("c2", _evidence(confidence=0.2))
        result = c.get_weak_evidence()
        assert set(result) == {"c1", "c2"}


# ---------------------------------------------------------------------------
# Constraint.to_search_terms
# ---------------------------------------------------------------------------


class TestConstraintToSearchTerms:
    """Verify search-term generation by constraint type."""

    def test_property(self):
        """PROPERTY -> raw value."""
        c = _constraint("c1", ConstraintType.PROPERTY, value="blue eyes")
        assert c.to_search_terms() == "blue eyes"

    def test_name_pattern(self):
        """NAME_PATTERN -> value + 'name trail mountain'."""
        c = _constraint("c1", ConstraintType.NAME_PATTERN, value="body part")
        result = c.to_search_terms()
        assert "body part" in result
        assert "name trail mountain" in result

    def test_event(self):
        """EVENT -> value + 'accident incident'."""
        c = _constraint("c1", ConstraintType.EVENT, value="fell from")
        result = c.to_search_terms()
        assert "fell from" in result
        assert "accident incident" in result

    def test_statistic(self):
        """STATISTIC -> value + 'statistics data'."""
        c = _constraint("c1", ConstraintType.STATISTIC, value="84.5x ratio")
        result = c.to_search_terms()
        assert "84.5x ratio" in result
        assert "statistics data" in result

    def test_other_type(self):
        """Other types -> str(value)."""
        c = _constraint("c1", ConstraintType.LOCATION, value="Colorado")
        assert c.to_search_terms() == "Colorado"


# ---------------------------------------------------------------------------
# Constraint.is_critical
# ---------------------------------------------------------------------------


class TestConstraintIsCritical:
    """Verify criticality determination."""

    def test_name_pattern_always_critical(self):
        """NAME_PATTERN is critical regardless of weight."""
        c = _constraint("c1", ConstraintType.NAME_PATTERN, weight=0.1)
        assert c.is_critical() is True

    def test_high_weight_critical(self):
        """Weight > 0.8 is critical for non-NAME_PATTERN."""
        c = _constraint("c1", ConstraintType.PROPERTY, weight=0.9)
        assert c.is_critical() is True

    def test_low_weight_not_critical(self):
        """Weight <= 0.8 is not critical for non-NAME_PATTERN."""
        c = _constraint("c1", ConstraintType.PROPERTY, weight=0.8)
        assert c.is_critical() is False

    def test_boundary_weight(self):
        """Weight exactly 0.8 -> not critical (> 0.8 required)."""
        c = _constraint("c1", ConstraintType.EVENT, weight=0.8)
        assert c.is_critical() is False


# ---------------------------------------------------------------------------
# EvidenceType.base_confidence
# ---------------------------------------------------------------------------


class TestEvidenceTypeBaseConfidence:
    """Verify base confidence mapping for each evidence type."""

    def test_direct_statement(self):
        assert EvidenceType.DIRECT_STATEMENT.base_confidence == 0.95

    def test_official_record(self):
        assert EvidenceType.OFFICIAL_RECORD.base_confidence == 0.90

    def test_research_finding(self):
        assert EvidenceType.RESEARCH_FINDING.base_confidence == 0.85

    def test_statistical_data(self):
        assert EvidenceType.STATISTICAL_DATA.base_confidence == 0.85

    def test_news_report(self):
        assert EvidenceType.NEWS_REPORT.base_confidence == 0.75

    def test_inference(self):
        assert EvidenceType.INFERENCE.base_confidence == 0.50

    def test_correlation(self):
        assert EvidenceType.CORRELATION.base_confidence == 0.30

    def test_speculation(self):
        assert EvidenceType.SPECULATION.base_confidence == 0.10
