"""The quality commands: `audit` (no network) and `verify` (re-reads, per host).

Registered into `app.cli` by one line so the operational and the quality
commands stay in their own files (Stream Q owns this one, Stream A owns
`cli.py`). Both commands are described in `docs/QUALITY.md`; the rules they
enforce come from build plan §3a and Decision 10.

    python -m app.cli audit [--out DIR] [--json] [--seed N] [--no-store]
    python -m app.cli verify [--n 20] [--source SLUG ...] [--seed N] [--mode after_collection|weekly|on_demand]
    python -m app.cli verify-status
    python -m app.cli verify-clear --check ID [ID ...] --note "why" [--by rian]

`verify` runs one host at a time in this process; to run hosts in parallel
start one process per `--source`. Run it the morning after a collection, not
immediately after (the retailer has just served us a whole crawl).
"""

import argparse
import json
import logging
from pathlib import Path

from app.db import SessionLocal
from app.services.audit import format_summary, run_audit, store_snapshot, write_snapshot
from app.services.verify import (
    DEFAULT_N,
    blocked_sources,
    clear_checks,
    pass_rates,
    run_verification,
    spot_checked,
)

# The workspace's verification folder when the command runs from a checkout
# (main/ is the instance; .logs/ sits beside it). Inside the container that
# folder does not exist and the snapshot goes to the table alone.
DEFAULT_OUT = Path(__file__).resolve().parents[2] / ".logs" / "verification"


def cmd_audit(args: argparse.Namespace) -> int:
    with SessionLocal() as db:
        snapshot = run_audit(db, seed=args.seed)
        stored = False if args.no_store else store_snapshot(db, snapshot)
    out_dir = Path(args.out) if args.out else (DEFAULT_OUT if DEFAULT_OUT.is_dir() else None)
    path = write_snapshot(snapshot, out_dir) if out_dir else None
    if args.json:
        print(json.dumps(snapshot, indent=1, default=str))
    else:
        print(format_summary(snapshot))
        print(f"stored={'table' if stored else 'no table yet'} file={path or '-'}")
    return 1 if snapshot["summary"]["over"] and args.fail_on_over else 0


def cmd_verify(args: argparse.Namespace) -> int:
    with SessionLocal() as db:
        run = run_verification(
            db, n=args.n, sources=args.source or None, seed=args.seed, mode=args.mode
        )
        blocked = blocked_sources(db)
        print(f"verification run {run.id} seed={run.seed} n={run.n} mode={run.mode}")
        for slug, summary in run.per_source.items():
            print(f"  {slug:24s} {_summary_line(summary)}")
        if blocked:
            print(f"publication blocked (uncleared correctness failures): {len(blocked)} source(s); "
                  "see verify-status, clear with verify-clear")
    return 0


def _summary_line(summary: dict) -> str:
    if summary.get("skipped"):
        return f"skipped: {summary['skipped']}"
    parts = [f"checked={summary.get('checked', 0)}/{summary.get('planned', 0)}"]
    for verdict in ("PASS", "PRICE_MOVED", "REVIEW", "GONE", "FETCH_ERROR", "MISMATCH_SIZE",
                    "MISMATCH_IDENTITY", "MISMATCH_CURRENCY", "PARSE_FAIL", "BLOCKED"):
        if summary.get(verdict):
            parts.append(f"{verdict}={summary[verdict]}")
    return " ".join(parts)


def cmd_verify_status(_: argparse.Namespace) -> int:
    from sqlalchemy import select

    from app.models import Source

    with SessionLocal() as db:
        rates = pass_rates(db)
        blocked = blocked_sources(db)
        for source in db.scalars(select(Source).order_by(Source.slug)):
            entry = rates.get(source.id)
            checked = spot_checked(db, source.id)
            state = "BLOCKED " if source.id in blocked else "        "
            rate = f"{entry['pass_rate']:.0%} of {entry['checks']}" if entry and entry["pass_rate"] is not None else "no checks"
            spot = checked.date().isoformat() if checked else "-"
            print(f"  {state}{source.slug:24s} rolling {rate:14s} spot-checked {spot}")
            for check in blocked.get(source.id, []):
                print(f"           check {check.id}: {check.verdict} {check.detail}")
    return 0


def cmd_verify_clear(args: argparse.Namespace) -> int:
    with SessionLocal() as db:
        cleared = clear_checks(db, args.check, by=args.by, note=args.note)
    print(f"cleared {cleared} check(s)")
    return 0


def register(sub: argparse._SubParsersAction) -> None:
    verify = sub.add_parser("verify", help="re-read a sample of published listings, one host at a time")
    verify.add_argument("--n", type=int, default=DEFAULT_N, help="listings per source (20 after a collection, 200 weekly)")
    verify.add_argument("--source", action="append", help="collector slug; repeatable; default every eligible source")
    verify.add_argument("--seed", type=int, default=None, help="sampling seed (stored; random by default)")
    verify.add_argument("--mode", default="on_demand", choices=["on_demand", "after_collection", "weekly"])
    verify.set_defaults(func=cmd_verify)

    status = sub.add_parser("verify-status", help="per-source rolling pass rate, blocks, spot-checked date")
    status.set_defaults(func=cmd_verify_status)

    clear = sub.add_parser("verify-clear", help="a human clears failing checks (who and when are recorded)")
    clear.add_argument("--check", type=int, nargs="+", required=True, help="check ids from verify-status")
    clear.add_argument("--note", required=True, help="what was looked at and why it is fine")
    clear.add_argument("--by", default="rian", help="account display name (default rian)")
    clear.set_defaults(func=cmd_verify_clear)

    audit = sub.add_parser("audit", help="no-network data audit: metrics against thresholds, review lists")
    audit.add_argument("--out", help=f"directory for the JSON snapshot (default {DEFAULT_OUT} when present)")
    audit.add_argument("--json", action="store_true", help="print the whole snapshot instead of the summary")
    audit.add_argument("--seed", type=int, default=0, help="sampling seed, so a review list is reproducible")
    audit.add_argument("--no-store", action="store_true", help="do not write audit_snapshots")
    audit.add_argument("--fail-on-over", action="store_true", help="exit 1 when any metric is over its threshold")
    audit.set_defaults(func=cmd_audit)
