"""CLI interface for abi-evals.

Usage:
    python -m abi_evals.cli run --eval-suite ... --evidence-pack ... --out ...
    python -m abi_evals.cli diff --golden-set ... --evidence-pack ...
    python -m abi_evals.cli bless --version ... --evidence-pack ...
        --i-understand-this-changes-baselines
    python -m abi_evals.cli list-goldens
    python -m abi_evals.cli prompt-eval --suite ... [--out ...]
    python -m abi_evals.cli canary --eval-suite ... --baseline-results ... [--model-config ...]
"""
from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

from abi_evals.cost_envelope_eval import CostEnvelopeSpec, validate_suite_cost_envelope
from abi_evals.failure_distiller import FailureDistiller
from abi_evals.goldens import bless_golden, diff_golden, list_golden_versions, load_golden_set
from abi_evals.model_canary import ModelCanary
from abi_evals.prompt_eval import run_prompt_eval_suite
from abi_evals.regression_store import RegressionStore
from abi_evals.runner import load_eval_suite, run_suite, write_results


def cmd_run(args: argparse.Namespace) -> int:
    suite = load_eval_suite(Path(args.eval_suite))

    evidence_pack = None
    if args.evidence_pack:
        ep_path = Path(args.evidence_pack)
        if ep_path.is_dir():
            # Load manifest from directory
            manifest_path = ep_path / "manifest.json"
            if manifest_path.exists():
                with open(manifest_path) as f:
                    evidence_pack = json.load(f)
            else:
                evidence_pack = {"dir": str(ep_path)}
        else:
            with open(ep_path) as f:
                evidence_pack = json.load(f)

    results = run_suite(
        suite,
        evidence_pack=evidence_pack,
        run_id=args.run_id or "00000000",
        timestamp=args.timestamp,
    )

    if args.out:
        write_results(results, Path(args.out))
        print(f"Results written to {args.out}")
    else:
        print(json.dumps(results, indent=2, ensure_ascii=False, sort_keys=True))

    summary = results["summary"]
    print(f"\nSummary: {summary['passed']}/{summary['total']} passed, "
          f"{summary['failed']} failed, {summary['errors']} errors")

    return 0 if summary["failed"] == 0 and summary["errors"] == 0 else 1


def cmd_diff(args: argparse.Namespace) -> int:
    golden_set = load_golden_set(args.golden_set, goldens_dir=args.goldens_dir)

    with open(args.evidence_pack) as f:
        evidence_pack = json.load(f)

    diffs = diff_golden(golden_set, evidence_pack)

    for d in diffs:
        icon = {"match": "OK", "mismatch": "XX", "missing_golden": "??", "missing_actual": "!!"}
        print(f"  [{icon.get(d['status'], '??')}] {d['key']}: {d['details']}")

    mismatches = sum(1 for d in diffs if d["status"] != "match")
    print(f"\n{len(diffs)} artifacts compared, {mismatches} difference(s)")
    return 1 if mismatches > 0 else 0


def cmd_bless(args: argparse.Namespace) -> int:
    with open(args.evidence_pack) as f:
        evidence_pack = json.load(f)

    try:
        result_dir = bless_golden(
            version=args.version,
            evidence_pack=evidence_pack,
            goldens_dir=args.goldens_dir,
            confirm_flag=args.i_understand_this_changes_baselines,
        )
        print(f"Golden set blessed at {result_dir}")
        return 0
    except ValueError as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1


def cmd_cost_envelope(args: argparse.Namespace) -> int:
    suite = load_eval_suite(Path(args.suite))
    results = run_suite(suite, run_id="cost0001", timestamp="1970-01-01T00:00:00Z")

    envelope = CostEnvelopeSpec(
        max_total_tokens=args.max_total_tokens,
        max_tokens_per_case=args.max_tokens_per_case,
        warn_threshold_pct=args.warn_threshold_pct,
    )

    case_tokens: dict[str, int] | None = None
    cost_summary = results.get("cost_summary")
    if cost_summary:
        case_tokens = cost_summary.get("case_token_map")

    report = validate_suite_cost_envelope(results, envelope, case_tokens=case_tokens)
    output = json.dumps(report.to_dict(), indent=2, ensure_ascii=False, sort_keys=True)

    if args.out:
        out_path = Path(args.out)
        out_path.parent.mkdir(parents=True, exist_ok=True)
        out_path.write_text(output + "\n", encoding="utf-8")
        print(f"Cost envelope report written to {args.out}")
    else:
        print(output)

    return 0 if report.status == "PASS" else 1


def cmd_list_goldens(args: argparse.Namespace) -> int:
    versions = list_golden_versions(goldens_dir=args.goldens_dir)
    if not versions:
        print("No golden versions found.")
        return 0
    for v in versions:
        print(f"  {v}")
    return 0


def cmd_prompt_eval(args: argparse.Namespace) -> int:
    suite_path = Path(args.suite)
    results = run_prompt_eval_suite(suite_path)

    if args.out:
        write_results(results, Path(args.out))
        print(f"Results written to {args.out}")
    else:
        print(json.dumps(results, indent=2, ensure_ascii=False, sort_keys=True))

    summary = results["summary"]
    print(f"\nSummary: {summary['passed']}/{summary['total']} passed, "
          f"{summary['failed']} failed, {summary['errors']} errors")

    return 0 if summary["failed"] == 0 and summary["errors"] == 0 else 1


def cmd_distill(args: argparse.Namespace) -> int:
    """Distill classified failures into regression eval test cases."""
    failures_path = Path(args.failures_json)
    store_dir = Path(args.store_dir) if args.store_dir else Path("evals/regression")

    # Initialize distiller and store
    distiller = FailureDistiller()
    store = RegressionStore(store_dir=store_dir)

    try:
        # Distill failures into eval cases
        print(f"Reading failures from {failures_path}...")
        eval_cases = distiller.distill(failures_path)
        print(f"Distilled {len(eval_cases)} failures into eval cases")

        # Store the cases (with deduplication)
        print(f"Storing cases in {store_dir}...")
        result = store.add_many(eval_cases, force=args.force)

        # Print summary
        print(f"\nDistilled {len(eval_cases)} failures into {result['added']} new eval cases")
        if result['skipped'] > 0:
            print(f"Skipped {result['skipped']} duplicate cases")

        # Print store location
        print(f"\nRegression evals stored in: {store.store_dir}")
        print(f"Index file: {store.index_path}")

        return 0

    except FileNotFoundError as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1
    except json.JSONDecodeError as e:
        print(f"Error parsing JSON: {e}", file=sys.stderr)
        return 1
    except Exception as e:
        print(f"Unexpected error: {e}", file=sys.stderr)
        return 1


def cmd_canary(args: argparse.Namespace) -> int:
    """Run model canary validation gate."""
    from datetime import datetime

    try:
        # Initialize canary
        canary = ModelCanary(
            eval_suite_path=args.eval_suite,
            baseline_results_path=args.baseline_results,
        )

        # Load model config
        if args.model_config:
            with open(args.model_config) as f:
                model_config = json.load(f)
        else:
            # Default empty config for testing
            model_config = {}

        # Run canary
        print(f"Running model canary...")
        print(f"  Eval suite: {args.eval_suite}")
        print(f"  Baseline: {args.baseline_results}")
        result = canary.run_canary(model_config)

        # Print verdict
        verdict_icons = {"PASS": "✓", "WARN": "⚠", "FAIL": "✗"}
        icon = verdict_icons.get(result.verdict, "?")
        print(f"\nMODEL CANARY: {icon} {result.verdict}")
        print(f"\n{result.details}")

        # Print metrics
        print(f"\nMetrics:")
        print(f"  Success rate (baseline): {result.success_rate_baseline:.1%}")
        print(f"  Success rate (new):      {result.success_rate_new:.1%}")
        print(f"  Success rate delta:      {result.success_rate_delta:+.1%}")
        print(f"  Regressions:             {result.regression_count}")
        print(f"  Improvements:            {result.improvement_count}")
        print(f"  Cost (baseline):         {result.cost_baseline} tokens")
        print(f"  Cost (new):              {result.cost_new} tokens")
        print(f"  Cost delta:              {result.cost_delta:+d} tokens")

        # Print regressions if any
        if result.regressions:
            print(f"\nRegressions:")
            for reg in result.regressions:
                print(f"  - {reg.case_id}: {reg.baseline_status} → {reg.new_status}")
                if reg.new_message:
                    print(f"    {reg.new_message}")

        # Print improvements if any
        if result.improvements:
            print(f"\nImprovements:")
            for imp in result.improvements:
                print(f"  + {imp.case_id}: {imp.baseline_status} → {imp.new_status}")

        # Write report
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        reports_dir = Path("evals/canary_reports")
        reports_dir.mkdir(parents=True, exist_ok=True)
        report_path = reports_dir / f"canary_{timestamp}.json"

        with open(report_path, "w") as f:
            json.dump(result.to_dict(), f, indent=2, ensure_ascii=False, sort_keys=True)
            f.write("\n")

        print(f"\nReport written to: {report_path}")

        # Print recommendation on FAIL
        if result.verdict == "FAIL":
            print("\nRecommendation:")
            print("  Model change introduces significant regressions.")
            print("  Do NOT update .abi/models.yaml until regressions are fixed.")
            return 1

        return 0

    except FileNotFoundError as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1
    except Exception as e:
        print(f"Unexpected error: {e}", file=sys.stderr)
        return 1


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="abi-evals",
        description="ABI Evals — eval harness with golden management",
    )
    parser.add_argument("--goldens-dir", type=Path, default=None)

    sub = parser.add_subparsers(dest="command", required=True)

    # run
    p_run = sub.add_parser("run", help="Run eval suite")
    p_run.add_argument("--eval-suite", required=True)
    p_run.add_argument("--evidence-pack", default=None)
    p_run.add_argument("--out", default=None)
    p_run.add_argument("--run-id", default=None)
    p_run.add_argument("--timestamp", default=None)

    # diff
    p_diff = sub.add_parser("diff", help="Diff golden set against evidence pack")
    p_diff.add_argument("--golden-set", required=True)
    p_diff.add_argument("--evidence-pack", required=True)

    # bless
    p_bless = sub.add_parser("bless", help="Promote outputs to golden baseline")
    p_bless.add_argument("--version", required=True)
    p_bless.add_argument("--evidence-pack", required=True)
    p_bless.add_argument("--i-understand-this-changes-baselines", action="store_true")

    # cost-envelope
    p_cost = sub.add_parser("cost-envelope", help="Validate suite cost envelope")
    p_cost.add_argument("--suite", required=True, help="Path to eval suite JSON")
    p_cost.add_argument("--max-total-tokens", type=int, required=True)
    p_cost.add_argument("--max-tokens-per-case", type=int, default=None)
    p_cost.add_argument("--warn-threshold-pct", type=float, default=0.80)
    p_cost.add_argument("--out", default=None, help="Output file path")

    # list-goldens
    sub.add_parser("list-goldens", help="List golden versions")

    # prompt-eval
    p_prompt = sub.add_parser("prompt-eval", help="Run prompt eval suite")
    p_prompt.add_argument("--suite", required=True, help="Path to prompt-eval-suite JSON")
    p_prompt.add_argument("--out", default=None, help="Output path for results JSON")

    # distill
    p_distill = sub.add_parser("distill", help="Distill failures into regression eval cases")
    p_distill.add_argument("failures_json", help="Path to failures.json file")
    p_distill.add_argument("--store-dir", default=None, help="Directory to store regression evals (default: evals/regression)")
    p_distill.add_argument("--force", action="store_true", help="Force add even if duplicates exist")

    # canary
    p_canary = sub.add_parser("canary", help="Run model canary validation gate")
    p_canary.add_argument("--eval-suite", required=True, help="Path to eval suite JSON")
    p_canary.add_argument("--baseline-results", required=True, help="Path to baseline results JSON")
    p_canary.add_argument("--model-config", default=None, help="Path to new model config JSON (optional)")

    return parser


def main(argv: list[str] | None = None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)

    if args.goldens_dir:
        args.goldens_dir = Path(args.goldens_dir)

    commands = {
        "run": cmd_run,
        "diff": cmd_diff,
        "bless": cmd_bless,
        "cost-envelope": cmd_cost_envelope,
        "list-goldens": cmd_list_goldens,
        "prompt-eval": cmd_prompt_eval,
        "distill": cmd_distill,
        "canary": cmd_canary,
    }

    return commands[args.command](args)


if __name__ == "__main__":
    sys.exit(main())
