"""
llm-guard-kit — command-line interface.

Commands:
    status      Show all monitored domains and their stats
    calibrate   Warm up a domain with chains (mixed-domain support)
    score       Score a single chain from a JSON file
    recalibrate Reset calibration after a model upgrade
    export      Export audit log (CSV or JSON)
    dashboard   Launch the monitoring dashboard (served via FastAPI)
    serve       Launch the FastAPI scoring/calibration API

Usage:
    llm-guard-kit status
    llm-guard-kit status --domain prod --db /var/data/qppg.db

    llm-guard-kit calibrate --domain prod --chains 25
    llm-guard-kit calibrate --domain prod --source-domain staging --chains 50

    llm-guard-kit score --question "What year?" --steps-file steps.json

    llm-guard-kit recalibrate --domain prod --new-model claude-opus-4-6

    llm-guard-kit export --domain prod --start 2026-01-01 --format csv

    llm-guard-kit dashboard --domain prod --port 8080
    llm-guard-kit serve --domain prod --port 8000
"""

from __future__ import annotations

import argparse
import json
import sys
import time
from pathlib import Path


# ── Helpers ───────────────────────────────────────────────────────────────────

def _default_db() -> str:
    return str(Path.home() / ".qppg" / "chains.db")


def _parse_date(s: str) -> float:
    """Parse YYYY-MM-DD string to Unix timestamp."""
    import datetime
    try:
        dt = datetime.datetime.strptime(s, "%Y-%m-%d")
        return dt.timestamp()
    except ValueError:
        try:
            return float(s)
        except ValueError:
            sys.exit(f"Error: cannot parse date '{s}'. Use YYYY-MM-DD or Unix timestamp.")


def _get_store(db_path: str):
    from .store import ChainStore
    return ChainStore(db_path)


# ── Sub-commands ──────────────────────────────────────────────────────────────

def cmd_status(args):
    """Show all domains and their stats."""
    store = _get_store(args.db)

    if args.domain:
        domains = [args.domain]
    else:
        domains = store.get_domains()

    if not domains:
        print("No domains found. Start tracking with QppgMonitor(db_path=...) first.")
        return

    from .drift import DriftDetector
    detector = DriftDetector()

    col = "{:<20} {:>8} {:>8} {:>8} {:>8} {:<12}"
    print(col.format("DOMAIN", "CHAINS", "ALERTS", "AVG RISK", "AUROC", "DRIFT"))
    print("-" * 72)
    for d in domains:
        stats = store.get_domain_stats(d)
        drift = detector.check(d, store)
        drift_str = f"{drift.delta:+.3f}" if drift else "OK"
        auroc_str = f"{stats['last_auroc']:.3f}" if stats["last_auroc"] else "n/a"
        print(col.format(
            d[:20],
            stats["n_chains"],
            stats["n_alerts"],
            f"{stats['avg_risk']:.3f}",
            auroc_str,
            drift_str,
        ))


def cmd_calibrate(args):
    """Warm up a domain's GMM calibration."""
    store = _get_store(args.db)

    if args.source_domain:
        # Mixed-domain: copy N chains from source domain
        print(f"Copying up to {args.chains} chains from '{args.source_domain}' → '{args.domain}'...")
        source_chains = store.get_calibration_pool(args.source_domain, n=args.chains)
        if not source_chains:
            sys.exit(f"No chains found in domain '{args.source_domain}'.")
        print(f"  Found {len(source_chains)} chains in '{args.source_domain}'.")
        for c in source_chains:
            store.add_chain(args.domain, c, risk_score=0.0)
        print(f"  Copied {len(source_chains)} chains into '{args.domain}'.")
    else:
        # Interactive: read chains from stdin or file
        if args.chains_file:
            text = Path(args.chains_file).read_text()
            chains = json.loads(text)
            print(f"Loaded {len(chains)} chains from {args.chains_file}.")
        else:
            print(f"Paste JSON array of chain dicts, then press Enter + Ctrl-D (or Ctrl-Z on Windows):")
            text = sys.stdin.read().strip()
            if not text:
                sys.exit("No input provided.")
            chains = json.loads(text)
            print(f"Loaded {len(chains)} chains.")

        for c in chains:
            store.add_chain(args.domain, c, risk_score=0.0)

    # Re-fit GMM
    pool = store.get_calibration_pool(args.domain, n=1000)
    if len(pool) < 5:
        print(f"  Only {len(pool)} chains in pool — need at least 5 to calibrate.")
        return

    from .label_free_scorer import LabelFreeScorer
    scorer = LabelFreeScorer()
    result = scorer.calibrate(pool)
    status = scorer.status()
    auroc_str = status.get("expected_auroc", "unknown")
    print(f"\nCalibration complete:")
    print(f"  Domain    : {args.domain}")
    print(f"  Pool size : {len(pool)}")
    print(f"  GMM fitted: {result.get('gmm_fitted', False)}")
    print(f"  Est. AUROC: {auroc_str}")
    store.log_calibration(args.domain, len(pool), 0.0)


def cmd_score(args):
    """Score a single chain."""
    if args.steps_file:
        data = json.loads(Path(args.steps_file).read_text())
        if isinstance(data, dict):
            question     = data.get("question", args.question or "")
            steps        = data.get("steps", [])
            final_answer = data.get("final_answer", "")
            finished     = data.get("finished", True)
        else:
            question     = args.question or ""
            steps        = data
            final_answer = ""
            finished     = True
    else:
        if not args.question:
            sys.exit("Provide --question or --steps-file.")
        question, steps, final_answer, finished = args.question, [], "", True

    from .label_free_scorer import LabelFreeScorer
    scorer = LabelFreeScorer()

    # Load calibration from store if available
    if args.domain:
        store = _get_store(args.db)
        pool  = store.get_calibration_pool(args.domain, n=200)
        if pool:
            scorer.calibrate(pool)

    result = scorer.score(question, steps, final_answer, finished)
    rq     = scorer.retrieval_quality(question, steps)

    print(f"\nRisk score   : {result.risk_score:.3f}  "
          f"({'ALERT' if result.needs_review else 'OK'})")
    print(f"Behavioral   : {result.behavioral_score:.3f}")
    if result.gmm_score is not None:
        print(f"GMM score    : {result.gmm_score:.3f}")
    print(f"Retrieval    : mean_sim={rq['mean_sim']:.3f}  "
          f"min_sim={rq['min_sim']:.3f}  ({rq['quality_label']})")
    n_search = sum(1 for s in steps if s.get("action_type") == "Search")
    print(f"Search steps : {n_search}")


def cmd_recalibrate(args):
    """Reset calibration after a model upgrade."""
    print(f"\n{'=' * 60}")
    print(f"  MODEL UPGRADE RECALIBRATION WARNING")
    print(f"{'=' * 60}")
    print(f"\n  Domain      : {args.domain}")
    print(f"  New model   : {args.new_model}")
    print()
    print("  IMPORTANT: Cross-model AUROC = 0.508 (≈ chance).")
    print("  Calibration from the old model does NOT transfer to the new model.")
    print("  You must collect new chains from the new model before re-calibrating.")
    print()

    if not args.yes:
        ans = input(f"  Clear calibration for '{args.domain}'? [y/N] ").strip().lower()
        if ans != "y":
            print("  Aborted.")
            return

    store = _get_store(args.db)
    n = store.clear_domain(args.domain)
    print(f"\n  Cleared {n} chains from '{args.domain}'.")
    print(f"  Next steps:")
    print(f"    1. Run your agent with {args.new_model} on 25+ queries")
    print(f"    2. Run: llm-guard-kit calibrate --domain {args.domain}")
    print(f"    3. Monitor AUROC with: llm-guard-kit status --domain {args.domain}")


def cmd_export(args):
    """Export audit log."""
    store  = _get_store(args.db)
    start  = _parse_date(args.start) if args.start else 0.0
    end    = _parse_date(args.end)   if args.end   else time.time()
    result = store.export_audit(args.domain, start, end, fmt=args.format)

    if args.output:
        Path(args.output).write_text(result)
        print(f"Exported to {args.output}")
    else:
        print(result)


def cmd_dashboard(args):
    """Launch the monitoring dashboard."""
    import subprocess
    host = getattr(args, "host", "127.0.0.1")
    port = getattr(args, "port", 8080)
    db   = args.db
    domain = args.domain

    # Launch server with dashboard enabled
    cmd = [
        sys.executable, "-m", "qppg_service.server",
        "--domain", domain,
        "--port", str(port),
        "--host", host,
        "--db", db,
        "--dashboard",
    ]
    print(f"Starting dashboard on http://{host}:{port}/dashboard")
    print(f"  Domain : {domain}")
    print(f"  DB     : {db}")
    print("  Press Ctrl-C to stop.\n")
    subprocess.run(cmd)


def cmd_serve(args):
    """Launch the FastAPI scoring/calibration API."""
    import subprocess
    cmd = [
        sys.executable, "-m", "qppg_service.server",
        "--domain", args.domain,
        "--port", str(args.port),
        "--host", args.host,
    ]
    if args.db:
        cmd += ["--db", args.db]
    print(f"Starting QPPG API on http://{args.host}:{args.port}")
    print(f"  Docs: http://{args.host}:{args.port}/docs\n")
    subprocess.run(cmd)


# ── Argument parser ───────────────────────────────────────────────────────────

def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="llm-guard-kit",
        description="LLM agent reliability monitoring — llm-guard-kit v0.3.0",
    )
    parser.add_argument("--db", default=_default_db(),
                        help=f"SQLite database path (default: {_default_db()})")

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

    # status
    p = sub.add_parser("status", help="Show monitored domains and stats")
    p.add_argument("--domain", default=None, help="Filter to a single domain")

    # calibrate
    p = sub.add_parser("calibrate", help="Warm up domain calibration")
    p.add_argument("--domain",        required=True)
    p.add_argument("--chains",        type=int, default=25,
                   help="Number of chains to use (default: 25)")
    p.add_argument("--source-domain", default=None,
                   help="Copy chains from this domain (mixed-domain warm-up)")
    p.add_argument("--chains-file",   default=None,
                   help="JSON file containing chain dicts to import")

    # score
    p = sub.add_parser("score", help="Score a single chain")
    p.add_argument("--question",   default=None)
    p.add_argument("--steps-file", default=None,
                   help="JSON file: list of step dicts or full chain dict")
    p.add_argument("--domain",     default=None,
                   help="Domain to load calibration from")

    # recalibrate
    p = sub.add_parser("recalibrate", help="Reset calibration after model upgrade")
    p.add_argument("--domain",    required=True)
    p.add_argument("--new-model", required=True, help="New model identifier")
    p.add_argument("--yes",       action="store_true", help="Skip confirmation prompt")

    # export
    p = sub.add_parser("export", help="Export audit log")
    p.add_argument("--domain",  required=True)
    p.add_argument("--start",   default=None, help="Start date YYYY-MM-DD")
    p.add_argument("--end",     default=None, help="End date YYYY-MM-DD")
    p.add_argument("--format",  choices=["csv", "json"], default="csv")
    p.add_argument("--output",  default=None, help="Output file (default: stdout)")

    # dashboard
    p = sub.add_parser("dashboard", help="Launch monitoring dashboard")
    p.add_argument("--domain", default="default")
    p.add_argument("--port",   type=int, default=8080)
    p.add_argument("--host",   default="127.0.0.1")

    # serve
    p = sub.add_parser("serve", help="Launch the scoring API server")
    p.add_argument("--domain", default="default")
    p.add_argument("--port",   type=int, default=8000)
    p.add_argument("--host",   default="127.0.0.1")

    return parser


def main():
    parser = build_parser()
    args   = parser.parse_args()

    dispatch = {
        "status":      cmd_status,
        "calibrate":   cmd_calibrate,
        "score":       cmd_score,
        "recalibrate": cmd_recalibrate,
        "export":      cmd_export,
        "dashboard":   cmd_dashboard,
        "serve":       cmd_serve,
    }
    dispatch[args.command](args)


if __name__ == "__main__":
    main()
