"""Precision/recall/F1 evaluation helpers for benchmark datasets."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Dict, Iterable, Set, Tuple


def _norm_key(rule_id: str, file_path: str, line: int) -> Tuple[str, str, int]:
    return (rule_id.strip(), file_path.replace("\\", "/").strip(), int(line))


def load_predictions(path: Path) -> Set[Tuple[str, str, int]]:
    """Load predictions from Oncecheck JSON export format."""
    data = json.loads(path.read_text())
    out: Set[Tuple[str, str, int]] = set()
    for scan in data.get("scans", []):
        for f in scan.get("findings", []):
            out.add(_norm_key(
                str(f.get("id", "")),
                str(f.get("file_path", "")),
                int(f.get("line", 0) or 0),
            ))
    return out


def load_truth(path: Path) -> Set[Tuple[str, str, int]]:
    """Load truth labels from simple JSON list format."""
    # Expected format:
    # {"truth":[{"id":"RULE","file_path":"src/a.ts","line":12}, ...]}
    data = json.loads(path.read_text())
    out: Set[Tuple[str, str, int]] = set()
    for item in data.get("truth", []):
        out.add(_norm_key(
            str(item.get("id", "")),
            str(item.get("file_path", "")),
            int(item.get("line", 0) or 0),
        ))
    return out


def filter_keys_by_prefixes(
    keys: Set[Tuple[str, str, int]],
    prefixes: Iterable[str],
) -> Set[Tuple[str, str, int]]:
    """Filter benchmark keys by rule-id prefixes."""
    pref = [p.strip().upper() for p in prefixes if p and p.strip()]
    if not pref:
        return set(keys)
    return {k for k in keys if any(k[0].upper().startswith(p) for p in pref)}


def score(pred: Set[Tuple[str, str, int]], truth: Set[Tuple[str, str, int]]) -> Dict[str, float]:
    tp = len(pred & truth)
    fp = len(pred - truth)
    fn = len(truth - pred)
    precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0
    recall = tp / (tp + fn) if (tp + fn) > 0 else 0.0
    f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0.0
    return {
        "tp": float(tp),
        "fp": float(fp),
        "fn": float(fn),
        "precision": precision,
        "recall": recall,
        "f1": f1,
    }


def evaluate_thresholds(
    result: Dict[str, float],
    *,
    min_precision: float,
    min_recall: float,
    min_f1: float,
    min_tp: int = 1,
) -> Dict[str, object]:
    """Evaluate whether benchmark metrics satisfy configured quality gates."""
    checks = {
        "precision": float(result.get("precision", 0.0)) >= float(min_precision),
        "recall": float(result.get("recall", 0.0)) >= float(min_recall),
        "f1": float(result.get("f1", 0.0)) >= float(min_f1),
        "tp": int(result.get("tp", 0.0)) >= int(min_tp),
    }
    failed = [name for name, ok in checks.items() if not ok]
    return {
        "ok": len(failed) == 0,
        "failed": failed,
        "checks": checks,
    }


def truth_template(suite: str) -> dict:
    """Return a template payload for public benchmark truth labels."""
    return {
        "suite": suite,
        "truth": [
            {"id": "WEB-OWASP-003", "file_path": "src/example.ts", "line": 42},
        ],
    }
