"""Evaluator: Rule-based & Reference-based 评估 + 评分聚合。"""

from __future__ import annotations

import re
from difflib import SequenceMatcher

from promptgrad.models import CaseScore, EvalResult, ExecutionResult, TestCase


class Evaluator:
    """评估器: 执行 rule-based 和 reference-based 评估。"""

    def __init__(self, rubric: dict, concurrency: int = 10):
        self.rubric = rubric
        self.concurrency = concurrency
        self.dimensions = rubric["dimensions"]
        self.pass_threshold = rubric["pass_threshold"]

    def _eval_rule(self, output: str, rules: list[dict]) -> float:
        """Rule-based 评估。所有规则通过返回 1.0，任一失败返回 0.0。"""
        for rule in rules:
            rule_type = rule["type"]
            if rule_type == "max_length":
                if len(output) > rule["value"]:
                    return 0.0
            elif rule_type == "min_length":
                if len(output) < rule["value"]:
                    return 0.0
            elif rule_type == "regex_match":
                if not re.search(rule["pattern"], output):
                    return 0.0
            elif rule_type == "no_prefix":
                if re.match(rule["pattern"], output):
                    return 0.0
        return 1.0

    def _eval_reference(self, output: str, golden: str, metric: str) -> float:
        """Reference-based 评估 (exact_match / fuzzy)。semantic 由宿主模型处理。"""
        if metric == "exact_match":
            return 1.0 if output.strip() == golden.strip() else 0.0
        elif metric == "fuzzy":
            return SequenceMatcher(None, output.strip(), golden.strip()).ratio()
        else:
            # semantic 等其他 metric 返回 -1 表示需要宿主模型处理
            return -1.0

    def evaluate(
        self,
        results: list[ExecutionResult],
        test_cases: list[TestCase],
        version: int = 0,
    ) -> EvalResult:
        """评估所有执行结果 (仅 rule + reference 维度)，返回部分评估结果。"""
        case_map = {tc.id: tc for tc in test_cases}
        case_scores: list[CaseScore] = []

        for er in results:
            tc = case_map.get(er.case_id)
            scores: dict[str, float] = {}
            critiques: list[str] = []

            for dim in self.dimensions:
                if dim["eval_method"] == "rule":
                    s = self._eval_rule(er.output, dim.get("rules", []))
                    scores[dim["name"]] = s
                    if s < 1.0:
                        critiques.append(f"{dim['name']}: 规则检查未通过")
                elif dim["eval_method"] == "reference":
                    if tc and tc.has_golden:
                        s = self._eval_reference(er.output, tc.golden, dim.get("metric", "fuzzy"))
                        if s >= 0:
                            scores[dim["name"]] = s
                            if s < self.pass_threshold:
                                critiques.append(f"{dim['name']}: 参考对比得分 {s:.2f}")
                        # s == -1 表示 semantic，留给宿主模型
                    # 无 golden 时跳过
                # llm_judge 维度留给宿主模型，此处不处理

            # 计算已有维度的加权分数
            weighted_score = self._calc_weighted_score(scores)
            passed = weighted_score >= self.pass_threshold
            case_scores.append(
                CaseScore(
                    case_id=er.case_id,
                    scores=scores,
                    weighted_score=weighted_score,
                    critique="; ".join(critiques) if critiques else "",
                    passed=passed,
                )
            )

        return self._aggregate(case_scores, version=version)

    def _calc_weighted_score(self, scores: dict[str, float]) -> float:
        """按已评估维度的权重计算加权分数。"""
        total_weight = 0.0
        total_score = 0.0
        for dim in self.dimensions:
            if dim["name"] in scores:
                total_weight += dim["weight"]
                total_score += dim["weight"] * scores[dim["name"]]
        if total_weight == 0:
            return 0.0
        return total_score / total_weight

    def _aggregate(self, case_scores: list[CaseScore], version: int) -> EvalResult:
        """聚合所有 case 的评分。"""
        if not case_scores:
            return EvalResult(
                version=version,
                case_scores=[],
                avg_score=0.0,
                pass_rate=0.0,
                dimension_breakdown={},
                worst_dimension="",
                failure_patterns=[],
            )

        avg_score = sum(cs.weighted_score for cs in case_scores) / len(case_scores)
        pass_rate = sum(1 for cs in case_scores if cs.passed) / len(case_scores)

        # 维度分解
        dim_scores: dict[str, list[float]] = {}
        for cs in case_scores:
            for dim_name, score in cs.scores.items():
                dim_scores.setdefault(dim_name, []).append(score)

        dimension_breakdown = {}
        worst_dim = ""
        worst_avg = float("inf")
        for dim_name, scores_list in dim_scores.items():
            avg = sum(scores_list) / len(scores_list)
            dimension_breakdown[dim_name] = {"avg": round(avg, 4), "min": round(min(scores_list), 4)}
            if avg < worst_avg:
                worst_avg = avg
                worst_dim = dim_name

        return EvalResult(
            version=version,
            case_scores=case_scores,
            avg_score=round(avg_score, 4),
            pass_rate=round(pass_rate, 4),
            dimension_breakdown=dimension_breakdown,
            worst_dimension=worst_dim,
            failure_patterns=[],  # 由宿主模型填充
        )
