"""
Main constraint checker that orchestrates constraint validation.

This module provides the primary interface for checking candidates against constraints.
"""

from dataclasses import dataclass
from typing import Dict, List, Optional

from langchain_core.language_models import BaseChatModel
from loguru import logger

from ..candidates.base_candidate import Candidate
from ..constraints.base_constraint import Constraint
from .evidence_analyzer import EvidenceAnalyzer
from .rejection_engine import RejectionEngine, RejectionResult


@dataclass
class ConstraintCheckResult:
    """Result of checking a candidate against all constraints."""

    candidate: Candidate
    total_score: float
    constraint_scores: Dict[str, Dict]
    rejection_result: Optional[RejectionResult]
    detailed_results: List[Dict]


class ConstraintChecker:
    """
    Main constraint checker that validates candidates against constraints.

    This checker:
    1. Gathers evidence for each constraint
    2. Analyzes evidence using dual confidence scoring
    3. Makes rejection decisions based on evidence
    4. Provides detailed scoring breakdown
    """

    def __init__(
        self,
        model: BaseChatModel,
        evidence_gatherer=None,  # Will be passed in from strategy
        negative_threshold: float = 0.25,
        positive_threshold: float = 0.4,
        uncertainty_penalty: float = 0.2,
        negative_weight: float = 0.5,
    ):
        """
        Initialize the constraint checker.

        Args:
            model: Language model for evidence analysis
            evidence_gatherer: Function to gather evidence (from strategy)
            negative_threshold: Rejection threshold for negative evidence
            positive_threshold: Minimum positive evidence required
            uncertainty_penalty: Penalty for uncertain evidence
            negative_weight: Weight for negative evidence in scoring
        """
        self.model = model
        self.evidence_gatherer = evidence_gatherer

        # Initialize components
        self.evidence_analyzer = EvidenceAnalyzer(model)
        self.rejection_engine = RejectionEngine(
            negative_threshold, positive_threshold
        )

        # Scoring parameters
        self.uncertainty_penalty = uncertainty_penalty
        self.negative_weight = negative_weight

    def check_candidate(
        self, candidate: Candidate, constraints: List[Constraint]
    ) -> ConstraintCheckResult:
        """
        Check a candidate against all constraints.

        Args:
            candidate: The candidate to check
            constraints: List of constraints to check against

        Returns:
            ConstraintCheckResult: Complete evaluation result
        """
        logger.info(f"Checking candidate: {candidate.name}")

        constraint_results = {}
        constraint_scores = {}
        detailed_results = []
        total_score = 0.0

        for constraint in constraints:
            # Gather evidence for this constraint
            evidence_list = self._gather_evidence_for_constraint(
                candidate, constraint
            )

            if evidence_list:
                # Analyze evidence with dual confidence
                dual_evidence = [
                    self.evidence_analyzer.analyze_evidence_dual_confidence(
                        e, constraint
                    )
                    for e in evidence_list
                ]

                constraint_results[constraint] = dual_evidence

                # Calculate average scores for this constraint
                avg_positive = sum(
                    e.positive_confidence for e in dual_evidence
                ) / len(dual_evidence)
                avg_negative = sum(
                    e.negative_confidence for e in dual_evidence
                ) / len(dual_evidence)
                avg_uncertainty = sum(
                    e.uncertainty for e in dual_evidence
                ) / len(dual_evidence)

                # Calculate constraint score
                score = self.evidence_analyzer.evaluate_evidence_list(
                    evidence_list,
                    constraint,
                    self.uncertainty_penalty,
                    self.negative_weight,
                )

                # Store results
                constraint_scores[constraint.value] = {
                    "total": score,
                    "positive": avg_positive,
                    "negative": avg_negative,
                    "uncertainty": avg_uncertainty,
                    "weight": constraint.weight,
                }

                detailed_results.append(
                    {
                        "constraint": constraint.value,
                        "score": score,
                        "positive": avg_positive,
                        "negative": avg_negative,
                        "uncertainty": avg_uncertainty,
                        "weight": constraint.weight,
                        "type": constraint.type.value,
                    }
                )

                # Log result
                symbol = "✓" if score >= 0.8 else "○" if score >= 0.5 else "✗"
                logger.info(
                    f"{symbol} {candidate.name} | {constraint.value}: {int(score * 100)}% "
                    f"(+{int(avg_positive * 100)}% -{int(avg_negative * 100)}% ?{int(avg_uncertainty * 100)}%)"
                )

            else:
                # No evidence found
                score = 0.5 - self.uncertainty_penalty

                constraint_scores[constraint.value] = {
                    "total": score,
                    "positive": 0.0,
                    "negative": 0.0,
                    "uncertainty": 1.0,
                    "weight": constraint.weight,
                }

                detailed_results.append(
                    {
                        "constraint": constraint.value,
                        "score": score,
                        "positive": 0.0,
                        "negative": 0.0,
                        "uncertainty": 1.0,
                        "weight": constraint.weight,
                        "type": constraint.type.value,
                    }
                )

                logger.info(
                    f"? {candidate.name} | {constraint.value}: No evidence found"
                )

        # Check for rejection
        rejection_result = self.rejection_engine.check_all_constraints(
            candidate, constraint_results
        )

        if rejection_result and rejection_result.should_reject:
            # Candidate should be rejected
            total_score = 0.0
        else:
            # Calculate weighted average score
            if detailed_results:
                weights = [r["weight"] for r in detailed_results]
                scores = [r["score"] for r in detailed_results]
                total_score = sum(
                    s * w for s, w in zip(scores, weights, strict=False)
                ) / sum(weights)

        logger.info(f"Final score for {candidate.name}: {total_score:.2%}")

        return ConstraintCheckResult(
            candidate=candidate,
            total_score=total_score,
            constraint_scores=constraint_scores,
            rejection_result=rejection_result,
            detailed_results=detailed_results,
        )

    def _gather_evidence_for_constraint(
        self, candidate: Candidate, constraint: Constraint
    ) -> List[Dict]:
        """Gather evidence for a constraint using the provided evidence gatherer."""
        if self.evidence_gatherer:
            return self.evidence_gatherer(candidate, constraint)
        else:
            logger.warning(
                "No evidence gatherer provided - cannot gather evidence"
            )
            return []
