"""
Enhanced strategy with dual confidence scoring (positive/negative) for better accuracy.
"""

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

from loguru import logger

from ..candidates.base_candidate import Candidate
from ..constraints.base_constraint import Constraint
from .smart_query_strategy import SmartQueryStrategy


@dataclass
class ConstraintEvidence:
    """Evidence for a constraint with dual confidence scores."""

    positive_confidence: float  # How sure we are the constraint IS satisfied
    negative_confidence: (
        float  # How sure we are the constraint is NOT satisfied
    )
    uncertainty: float  # How uncertain we are (neither positive nor negative)
    evidence_text: str
    source: str


class DualConfidenceStrategy(SmartQueryStrategy):
    """
    Enhanced strategy that uses dual confidence scoring:
    - Positive confidence: Evidence that constraint IS satisfied
    - Negative confidence: Evidence that constraint is NOT satisfied
    - Uncertainty: Lack of clear evidence either way

    This allows for more accurate scoring and better early stopping decisions.
    """

    def __init__(
        self,
        *args,
        uncertainty_penalty: float = 0.2,
        negative_weight: float = 0.5,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)
        self.uncertainty_penalty = uncertainty_penalty
        self.negative_weight = negative_weight

    def _evaluate_evidence(
        self, evidence_list: List[Dict], constraint: Constraint
    ) -> float:
        """Enhanced evidence evaluation with dual confidence."""
        if not evidence_list:
            # No evidence means high uncertainty
            return 0.5 - self.uncertainty_penalty

        # Convert evidence to dual confidence format
        constraint_evidence = []
        for evidence in evidence_list:
            dual_evidence = self._analyze_evidence_dual_confidence(
                evidence, constraint
            )
            constraint_evidence.append(dual_evidence)

        # Calculate overall score
        total_positive = sum(e.positive_confidence for e in constraint_evidence)
        total_negative = sum(e.negative_confidence for e in constraint_evidence)
        total_uncertainty = sum(e.uncertainty for e in constraint_evidence)

        # Normalize
        evidence_count = len(constraint_evidence)
        avg_positive = total_positive / evidence_count
        avg_negative = total_negative / evidence_count
        avg_uncertainty = total_uncertainty / evidence_count

        # Calculate final score
        # High positive + low negative = high score
        # Low positive + high negative = low score
        # High uncertainty = penalty
        score = (
            avg_positive
            - (avg_negative * self.negative_weight)
            - (avg_uncertainty * self.uncertainty_penalty)
        )

        # Clamp to [0, 1]
        return max(0.0, min(1.0, score))

    def _analyze_evidence_dual_confidence(
        self, evidence: Dict, constraint: Constraint
    ) -> ConstraintEvidence:
        """Analyze evidence to extract dual confidence scores."""
        text = evidence.get("text", "")

        # Use LLM to analyze evidence with dual confidence
        prompt = f"""
Analyze this evidence for the constraint "{constraint.value}" (type: {constraint.type.value}).

Evidence:
{text[:1000]}

Provide three confidence scores (0-1):
1. POSITIVE_CONFIDENCE: How confident are you that this constraint IS satisfied?
2. NEGATIVE_CONFIDENCE: How confident are you that this constraint is NOT satisfied?
3. UNCERTAINTY: How uncertain are you (lack of clear evidence)?

The three scores should approximately sum to 1.0.

Format:
POSITIVE: [score]
NEGATIVE: [score]
UNCERTAINTY: [score]
"""

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

            # Extract scores
            positive = self._extract_score(response, "POSITIVE")
            negative = self._extract_score(response, "NEGATIVE")
            uncertainty = self._extract_score(response, "UNCERTAINTY")

            # Normalize if needed
            total = positive + negative + uncertainty
            if total > 0:
                positive /= total
                negative /= total
                uncertainty /= total
            else:
                # Default to high uncertainty
                uncertainty = 0.8
                positive = 0.1
                negative = 0.1

            return ConstraintEvidence(
                positive_confidence=positive,
                negative_confidence=negative,
                uncertainty=uncertainty,
                evidence_text=text[:500],
                source=evidence.get("source", "search"),
            )

        except Exception:
            logger.exception("Error analyzing evidence")
            # Default to high uncertainty
            return ConstraintEvidence(
                positive_confidence=0.1,
                negative_confidence=0.1,
                uncertainty=0.8,
                evidence_text=text[:500],
                source=evidence.get("source", "search"),
            )

    def _extract_score(self, text: str, label: str) -> float:
        """Extract confidence score from LLM response."""
        pattern = rf"{label}:\s*\[?(\d*\.?\d+)\]?"
        match = re.search(pattern, text, re.IGNORECASE)
        if match:
            try:
                return float(match.group(1))
            except (ValueError, TypeError):
                pass
        return 0.1  # Default low score

    def _gather_evidence_for_constraint(
        self, candidate: Candidate, constraint: Constraint
    ) -> List[Dict]:
        """Enhanced evidence gathering with targeted searches."""
        evidence = []

        # Build specific queries for this constraint
        queries = [
            f'"{candidate.name}" {constraint.value} verification',
            f'"{candidate.name}" {constraint.value} true or false',
            f"Is {candidate.name} {constraint.value}?",
        ]

        # For negative evidence, also search for contradictions
        if constraint.type.value == "property":
            queries.append(f'"{candidate.name}" NOT {constraint.value}')
            queries.append(f'"{candidate.name}" opposite of {constraint.value}')

        # Execute searches
        for query in queries[:3]:  # Limit searches
            if query.lower() not in self.searched_queries:
                self.searched_queries.add(query.lower())
                try:
                    results = self._execute_search(query)
                    content = results.get("current_knowledge", "")

                    if content:
                        evidence.append(
                            {
                                "text": content,
                                "source": f"search: {query}",
                                "query": query,
                            }
                        )
                except Exception:
                    logger.exception(f"Error gathering evidence for {query}")

        return evidence

    def _evaluate_candidate_immediately(self, candidate: Candidate) -> float:
        """Enhanced evaluation with detailed constraint scoring."""
        try:
            logger.info(
                f"Evaluating candidate: {candidate.name} with dual confidence"
            )

            total_score = 0.0
            constraint_scores = []
            detailed_results = []

            for i, constraint in enumerate(self.constraint_ranking):
                # Gather evidence
                evidence = self._gather_evidence_for_constraint(
                    candidate, constraint
                )

                # Evaluate with dual confidence
                score = self._evaluate_evidence(evidence, constraint)
                constraint_scores.append(score)

                # Store detailed results
                if evidence:
                    dual_evidence = [
                        self._analyze_evidence_dual_confidence(e, constraint)
                        for e in evidence
                    ]
                    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)

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

                    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)}%)"
                    )

                # Early exit for critical failures with high negative confidence
                if score < 0.2 and constraint.weight > 0.8:
                    logger.info(
                        f"Critical constraint failed with high confidence: {constraint.value}"
                    )
                    break

            # Calculate weighted average
            if constraint_scores:
                weights = [
                    c.weight
                    for c in self.constraint_ranking[: len(constraint_scores)]
                ]
                total_score = sum(
                    s * w
                    for s, w in zip(constraint_scores, weights, strict=False)
                ) / sum(weights)

            # Log detailed breakdown
            logger.info(f"\nDetailed analysis for {candidate.name}:")
            for result in detailed_results:
                logger.info(
                    f"  {result['constraint']}: {result['score']:.2%} "
                    f"(+{result['positive']:.0%} -{result['negative']:.0%} ?{result['uncertainty']:.0%})"
                )

            # Update best candidate if needed
            with self.evaluation_lock:
                self.evaluated_candidates[candidate.name] = total_score

                if total_score > self.best_score:
                    self.best_score = total_score
                    self.best_candidate = candidate.name

                    logger.info(
                        f"New best: {candidate.name} with {total_score:.2%}"
                    )

                    # Check for early stop
                    if total_score >= self.early_stop_threshold:
                        logger.info(
                            f"EARLY STOP: {candidate.name} reached {total_score:.2%}!"
                        )
                        self.found_answer.set()

                        if self.progress_callback:
                            self.progress_callback(
                                f"Found answer: {candidate.name} ({int(total_score * 100)}% confidence)",
                                95,
                                {
                                    "phase": "early_stop",
                                    "final_answer": candidate.name,
                                    "confidence": total_score,
                                    "detailed_results": detailed_results,
                                },
                            )

            return total_score

        except Exception:
            logger.exception(f"Error evaluating candidate: {candidate.name}")
            return 0.0
