from __future__ import annotations

import argparse
import json
import sys
from collections import Counter
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from app.core.bootstrap import build_intent_registry
from app.services.joint_nlu import JointBertNLU


DEFAULT_TEST_PATH = PROJECT_ROOT / "app/data/bert_intent_multilabel_eval_independent.jsonl"


def load_cases(path: Path) -> list[dict[str, object]]:
    rows: list[dict[str, object]] = []
    for line in path.read_text(encoding="utf-8").splitlines():
        line = line.strip()
        if not line:
            continue
        payload = json.loads(line)
        rows.append(payload)
    return rows


def main() -> None:
    parser = argparse.ArgumentParser(description="Joint BERT 多意图独立评测")
    parser.add_argument("--model-path", type=str, default="models/local_joint_bert_nlu")
    parser.add_argument("--test-path", type=str, default=str(DEFAULT_TEST_PATH))
    args = parser.parse_args()

    registry = build_intent_registry()
    nlu = JointBertNLU(model_path=args.model_path)
    cases = load_cases(Path(args.test_path))

    tp = 0
    fp = 0
    fn = 0
    exact = 0
    failures: list[dict[str, object]] = []
    category_correct: Counter[str] = Counter()
    category_total: Counter[str] = Counter()

    for case in cases:
        text = str(case["text"])
        expected = sorted({str(item) for item in case.get("expected_intent_ids", [])})
        predicted = sorted(item.intent_id for item in nlu.predict_multi_intents(text, registry.list(), top_k=8, max_labels=4))
        expected_set = set(expected)
        predicted_set = set(predicted)
        tp += len(expected_set & predicted_set)
        fp += len(predicted_set - expected_set)
        fn += len(expected_set - predicted_set)
        category = str(case.get("category") or "unknown")
        category_total[category] += 1
        if expected_set == predicted_set:
            exact += 1
            category_correct[category] += 1
        else:
            failures.append(
                {
                    "text": text,
                    "expected_intent_ids": expected,
                    "predicted_intent_ids": predicted,
                    "category": category,
                }
            )

    precision = tp / max(tp + fp, 1)
    recall = tp / max(tp + fn, 1)
    f1 = (2 * precision * recall / (precision + recall)) if (precision + recall) else 0.0
    result = {
        "sample_count": len(cases),
        "micro_precision": round(precision, 4),
        "micro_recall": round(recall, 4),
        "micro_f1": round(f1, 4),
        "exact_match": round(exact / max(len(cases), 1), 4),
        "per_category_exact_match": {
            category: round(category_correct[category] / max(total, 1), 4)
            for category, total in sorted(category_total.items())
        },
        "failures": failures[:20],
    }
    print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
