"""
SmartSelector — scoring-based judge selection using EnrichedContext.

Replaces JudgeSelector's span-type mapping with weighted multi-factor scoring.
Legacy JudgeSelector preserved as fallback.
"""

import logging
from typing import TYPE_CHECKING

from .models import EnrichedContext, SelectedJudge

if TYPE_CHECKING:
    from .selector import JudgeSelector

logger = logging.getLogger("aigie.judge.smart_selector")

# Issue type prefix → domain mapping
_ISSUE_TO_DOMAIN: dict[str, str] = {
    "hallucination": "hallucination",
    "drift": "drift",
    "safety": "safety",
    "quality": "quality",
    "error": "tool_usage",
    "loop": "loop",
    "tool": "tool_usage",
}

# Signal hint → relevant domains
_SIGNAL_DOMAINS: dict[str, list[str]] = {
    "refusal": ["quality", "safety"],
    "safety_triggered": ["safety"],
    "hallucination_marker": ["hallucination"],
    "lazy_response": ["quality"],
    "suspiciously_short": ["quality"],
}

# Default candidate judges (domain → judge metadata)
_DEFAULT_JUDGES: dict[str, dict] = {
    "hallucination": {"judge_id": "hallucination_judge", "name": "Hallucination Judge"},
    "drift": {"judge_id": "drift_judge", "name": "Drift Judge"},
    "safety": {"judge_id": "safety_judge", "name": "Safety Judge"},
    "quality": {"judge_id": "quality_judge", "name": "Quality Judge"},
    "tool_usage": {"judge_id": "tool_usage_judge", "name": "Tool Usage Judge"},
    "loop": {"judge_id": "loop_judge", "name": "Loop Detection Judge"},
}


class SmartSelector:
    """Scores and selects judges based on EnrichedContext.

    Scoring factors (weights sum to 100):
    - Signal match: 30   — judge handles a FastDetector signal
    - Workflow history: 25 — issue type common in this workflow
    - Step position: 15  — early → drift, late → quality
    - Previous step cascade: 15 — keep same judge if prev step had issue
    - Customer pattern: 10 — customer's top issues
    - Judge accuracy: 5   — historical accuracy (Phase 2)
    """

    SCORE_THRESHOLD = 30
    MAX_JUDGES = 3

    def __init__(self, legacy_selector: "JudgeSelector | None" = None):
        self._legacy = legacy_selector

    def select(self, ctx: EnrichedContext) -> list[SelectedJudge]:
        """Select 1-3 judges for this step. Returns ordered by relevance score."""
        # Fallback: no enrichment available
        if (
            not ctx.signal_hints
            and not ctx.workflow_id
            and not ctx.previous_step_had_issue
            and not ctx.workflow_common_issues
            and not ctx.customer_top_issues
        ):
            return self._legacy_select(ctx)

        scores: dict[str, float] = {}

        for domain in _DEFAULT_JUDGES:
            score = self._score_domain(domain, ctx)
            if score > 0:
                scores[domain] = score

        # Safety is non-negotiable when signal present
        if ctx.has_signal("safety_triggered"):
            scores["safety"] = max(scores.get("safety", 0), 100.0)

        if not scores:
            return self._legacy_select(ctx)

        # Sort by score, take top MAX_JUDGES above threshold
        sorted_domains = sorted(scores.items(), key=lambda x: x[1], reverse=True)
        selected = []
        for domain, score in sorted_domains:
            if score >= self.SCORE_THRESHOLD and len(selected) < self.MAX_JUDGES:
                meta = _DEFAULT_JUDGES[domain]
                selected.append(
                    SelectedJudge(
                        judge_id=meta["judge_id"],
                        name=meta["name"],
                        domain=domain,
                        relevance_score=score,
                    )
                )

        if not selected:
            return self._legacy_select(ctx)

        return selected

    def _score_domain(self, domain: str, ctx: EnrichedContext) -> float:
        """Score a judge domain against the enriched context."""
        score = 0.0

        # Factor 1: Signal match (weight: 30)
        for signal in ctx.signal_hints:
            if domain in _SIGNAL_DOMAINS.get(signal, []):
                score += 30.0
                break

        # Factor 2: Workflow history (weight: 25)
        for issue in ctx.workflow_common_issues:
            issue_domain = self._issue_to_domain(issue)
            if issue_domain == domain:
                score += 25.0
                break

        # Factor 3: Step position (weight: 15)
        if ctx.is_first_step:
            if domain == "drift":
                score += 15.0
            elif domain == "quality":
                score += 5.0
        elif ctx.is_deep_in_trace:
            if domain == "quality":
                score += 15.0
            elif domain == "loop":
                score += 10.0
        else:
            # Mid-trace: moderate boost to all
            score += 5.0

        # Factor 4: Previous step cascade (weight: 15)
        if ctx.previous_step_had_issue and ctx.previous_step_issue_type:
            prev_domain = self._issue_to_domain(ctx.previous_step_issue_type)
            if prev_domain == domain:
                score += 15.0
                if ctx.consecutive_issues >= 2:
                    score += 5.0

        # Factor 5: Customer pattern (weight: 10)
        for issue in ctx.customer_top_issues:
            issue_domain = self._issue_to_domain(issue)
            if issue_domain == domain:
                score += 10.0
                break

        # Factor 6: Judge accuracy (weight: 15, graduated)
        accuracy = ctx.judge_accuracy_scores.get(domain)
        if accuracy is not None:
            if accuracy >= 0.8:
                score += 15.0  # Highly accurate — strong boost
            elif accuracy >= 0.6:
                score += 10.0  # Good accuracy — moderate boost
            elif accuracy >= 0.4:
                score += 3.0  # Mediocre — small boost
            else:
                score -= 5.0  # Poor accuracy — penalize

        # Span-type relevance bonus
        if ctx.span_type == "tool" and domain == "tool_usage":
            score += 10.0
        elif ctx.span_type == "llm" and domain in ("hallucination", "quality", "drift"):
            score += 5.0

        return score

    def _issue_to_domain(self, issue_type: str) -> str:
        """Map issue type string to domain."""
        issue_lower = issue_type.lower()
        for prefix, domain in _ISSUE_TO_DOMAIN.items():
            if prefix in issue_lower:
                return domain
        return "quality"

    def _legacy_select(self, ctx: EnrichedContext) -> list[SelectedJudge]:
        """Fallback to legacy JudgeSelector."""
        if not self._legacy:
            meta = _DEFAULT_JUDGES["quality"]
            return [
                SelectedJudge(
                    judge_id=meta["judge_id"],
                    name=meta["name"],
                    domain="quality",
                    relevance_score=20.0,
                )
            ]

        categories = (
            self._legacy.select_with_signals(
                ctx.span_type,
                signal_hints=ctx.signal_hints,
            )
            if ctx.signal_hints
            else self._legacy.select(ctx.span_type)
        )

        _CAT_TO_DOMAIN = {
            "accuracy": "hallucination",
            "quality": "quality",
            "relevance": "quality",
            "safety-monitor": "safety",
            "tool-usage": "tool_usage",
            "error-handling": "tool_usage",
            "efficiency": "quality",
        }

        selected = []
        seen_domains: set[str] = set()
        for cat in categories[: self.MAX_JUDGES]:
            domain = _CAT_TO_DOMAIN.get(cat, "quality")
            if domain in seen_domains:
                continue
            seen_domains.add(domain)
            meta = _DEFAULT_JUDGES.get(domain, {"judge_id": f"{cat}_judge", "name": f"{cat} Judge"})
            selected.append(
                SelectedJudge(
                    judge_id=meta.get("judge_id", f"{cat}_judge"),
                    name=meta.get("name", f"{cat} Judge"),
                    domain=domain,
                    relevance_score=20.0,
                )
            )

        return selected or [
            SelectedJudge(
                judge_id="quality_judge",
                name="Quality Judge",
                domain="quality",
                relevance_score=20.0,
            )
        ]
