"""
Strict constraint checker - example of creating a custom variant.

This implementation is very strict about constraint satisfaction,
requiring high confidence for all constraints.
"""

from typing import Dict, List, Tuple

from loguru import logger

from ..candidates.base_candidate import Candidate
from ..constraints.base_constraint import Constraint, ConstraintType
from .base_constraint_checker import (
    BaseConstraintChecker,
    ConstraintCheckResult,
)


class StrictChecker(BaseConstraintChecker):
    """
    Strict constraint checker that requires high confidence for all constraints.

    This is an example of how to create custom constraint checking variants
    by inheriting from BaseConstraintChecker.
    """

    def __init__(
        self,
        *args,
        strict_threshold: float = 0.9,  # Very high threshold
        name_pattern_required: bool = True,  # NAME_PATTERN constraints are mandatory
        **kwargs,
    ):
        """
        Initialize strict checker.

        Args:
            strict_threshold: Very high threshold for all constraints
            name_pattern_required: Whether NAME_PATTERN constraints are mandatory
        """
        super().__init__(*args, **kwargs)
        self.strict_threshold = strict_threshold
        self.name_pattern_required = name_pattern_required

    def check_candidate(
        self, candidate: Candidate, constraints: List[Constraint]
    ) -> ConstraintCheckResult:
        """Check candidate with strict requirements."""
        logger.info(f"Checking candidate: {candidate.name} (strict mode)")

        constraint_scores = {}
        detailed_results = []
        rejection_reason = None
        should_reject = False

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

            # Calculate score
            score = self._evaluate_constraint_strictly(
                candidate, constraint, evidence_list
            )

            # Check for rejection
            reject, reason = self.should_reject_candidate(
                candidate, constraint, evidence_list
            )

            if reject and not should_reject:
                should_reject = True
                rejection_reason = reason

            # Store results
            constraint_scores[constraint.value] = {
                "total": score,
                "strict_pass": score >= self.strict_threshold,
                "weight": constraint.weight,
            }

            detailed_results.append(
                {
                    "constraint": constraint.value,
                    "score": score,
                    "strict_pass": score >= self.strict_threshold,
                    "weight": constraint.weight,
                    "type": constraint.type.value,
                }
            )

            self._log_constraint_result(candidate, constraint, score, {})

        # Calculate total score
        if should_reject:
            total_score = 0.0
        else:
            # All constraints must pass strict threshold
            all_pass = all(r["strict_pass"] for r in detailed_results)
            total_score = 1.0 if all_pass else 0.0

        logger.info(
            f"Strict evaluation for {candidate.name}: {'PASS' if total_score > 0 else 'FAIL'}"
        )

        return ConstraintCheckResult(
            candidate=candidate,
            total_score=total_score,
            constraint_scores=constraint_scores,
            should_reject=should_reject,
            rejection_reason=rejection_reason,
            detailed_results=detailed_results,
        )

    def should_reject_candidate(
        self,
        candidate: Candidate,
        constraint: Constraint,
        evidence_data: List[Dict],
    ) -> Tuple[bool, str]:
        """Strict rejection rules."""
        if not evidence_data:
            return True, f"No evidence for constraint '{constraint.value}'"

        score = self._evaluate_constraint_strictly(
            candidate, constraint, evidence_data
        )

        # Special handling for NAME_PATTERN constraints
        if (
            constraint.type == ConstraintType.NAME_PATTERN
            and self.name_pattern_required
        ):
            if score < 0.95:  # Even stricter for name patterns
                return (
                    True,
                    f"NAME_PATTERN constraint '{constraint.value}' failed strict evaluation",
                )

        # General strict threshold
        if score < self.strict_threshold:
            return (
                True,
                f"Constraint '{constraint.value}' below strict threshold ({score:.0%})",
            )

        return False, ""

    def _evaluate_constraint_strictly(
        self,
        candidate: Candidate,
        constraint: Constraint,
        evidence_list: List[Dict],
    ) -> float:
        """Evaluate constraint with strict criteria."""
        if not evidence_list:
            return 0.0

        # For NAME_PATTERN constraints, use direct name checking
        if constraint.type == ConstraintType.NAME_PATTERN:
            return self._check_name_pattern_strictly(
                candidate.name, constraint.value
            )

        # For other constraints, use LLM with strict prompt
        combined_evidence = "\n".join(
            [e.get("text", "")[:300] for e in evidence_list[:2]]
        )

        prompt = f"""
STRICT EVALUATION: Does "{candidate.name}" definitely and clearly satisfy: "{constraint.value}"?

Evidence:
{combined_evidence}

Be very strict. Only return a high score if there is clear, unambiguous evidence.

Score (0.0-1.0):
"""

        try:
            response = self.model.invoke(prompt).content.strip()
            import re

            match = re.search(r"(\d*\.?\d+)", response)
            if match:
                return max(0.0, min(float(match.group(1)), 1.0))
        except Exception:
            logger.exception("Error in strict evaluation")
            return 0.5  # Uncertain on error, don't auto-reject

        return 0.0

    def _check_name_pattern_strictly(
        self, candidate_name: str, pattern_description: str
    ) -> float:
        """Strict name pattern checking."""
        # Example: Check for body parts in name
        if "body part" in pattern_description.lower():
            body_parts = [
                "arm",
                "leg",
                "foot",
                "feet",
                "hand",
                "eye",
                "ear",
                "nose",
                "mouth",
                "tooth",
                "teeth",
                "head",
                "face",
                "neck",
                "back",
                "chest",
                "heart",
                "finger",
                "thumb",
                "toe",
                "knee",
                "elbow",
                "shoulder",
                "spine",
                "bone",
            ]

            name_lower = candidate_name.lower()
            for part in body_parts:
                if part in name_lower.split() or part in name_lower:
                    logger.info(
                        f"✓ Found body part '{part}' in '{candidate_name}'"
                    )
                    return 1.0

            logger.info(f"✗ No body part found in '{candidate_name}'")
            return 0.0

        # For other name patterns, use LLM
        prompt = f"""
Does the name "{candidate_name}" match this pattern: "{pattern_description}"?

Be very strict. Return 1.0 only if it clearly matches, 0.0 otherwise.

Score:
"""

        try:
            response = self.model.invoke(prompt).content.strip()
            import re

            match = re.search(r"(\d*\.?\d+)", response)
            if match:
                return max(0.0, min(float(match.group(1)), 1.0))
        except Exception:
            logger.warning(
                f"LLM constraint scoring failed for '{candidate_name}', "
                "returning uncertain score",
            )
            return 0.5

        return 0.0
