"""Rule-based failure pattern detectors for report suggestions."""

from __future__ import annotations

from evalkit.models import EvaluationResult


def detect_long_context(
    metric_name: str, failures: list[tuple[float, EvaluationResult]], overall_avg: float
) -> str | None:
    if not failures:
        return None
    long = sum(1 for _, r in failures if len(r.test_case.context) > 5)
    ratio = long / len(failures)
    if ratio > 0.6:
        return f"{ratio:.0%} of failures involve contexts with >5 chunks — consider reducing retrieval top-k or improving chunk relevance filtering."
    return None


def detect_short_answer(
    metric_name: str, failures: list[tuple[float, EvaluationResult]], overall_avg: float
) -> str | None:
    pairs = [(r.test_case.answer, r.test_case.expected_answer) for _, r in failures if r.test_case.expected_answer]
    if len(pairs) < 3:
        return None
    avg_ratio = sum(len(a) / max(len(e), 1) for a, e in pairs) / len(pairs)
    if avg_ratio < 0.3:
        return f"Failing answers average {avg_ratio:.0%} the length of expected answers — responses may be too terse."
    return None


def detect_empty_context(
    metric_name: str, failures: list[tuple[float, EvaluationResult]], overall_avg: float
) -> str | None:
    if not failures:
        return None
    empty = sum(1 for _, r in failures if not r.test_case.context)
    ratio = empty / len(failures)
    if ratio > 0.5:
        return f"{ratio:.0%} of failures have empty context — ensure your retrieval pipeline is returning results."
    return None


def detect_score_cliff(
    metric_name: str, failures: list[tuple[float, EvaluationResult]], overall_avg: float
) -> str | None:
    if not failures or overall_avg < 0.7:
        return None
    failure_avg = sum(s for s, _ in failures) / len(failures)
    if failure_avg < 0.2:
        return (
            f"Bimodal scores: overall average is {overall_avg:.2f} but failures "
            f"average {failure_avg:.2f} — a few cases fail catastrophically while most pass."
        )
    return None


def detect_tool_name_mismatch(
    metric_name: str, failures: list[tuple[float, EvaluationResult]], overall_avg: float
) -> str | None:
    if metric_name != "tool_selection":
        return None
    mismatches: list[str] = []
    for _, r in failures:
        expected = r.test_case.expected_tool_calls or []
        actual = r.test_case.actual_tool_calls or []
        for e, a in zip(expected, actual):
            if e.name != a.name:
                mismatches.append(f"{a.name} instead of {e.name}")
    if mismatches:
        top = ", ".join(sorted(set(mismatches))[:3])
        return f"Common tool mismatches: {top}."
    return None


DETECTORS = [
    detect_long_context,
    detect_short_answer,
    detect_empty_context,
    detect_score_cliff,
    detect_tool_name_mismatch,
]


def run_all_detectors(
    metric_name: str,
    failures: list[tuple[float, EvaluationResult]],
    overall_avg: float = 0.0,
) -> list[str]:
    patterns: list[str] = []
    for detector in DETECTORS:
        result = detector(metric_name, failures, overall_avg)
        if result is not None:
            patterns.append(result)
    return patterns
