"""The review's commands (Stream K4): `proposals load`, `proposals withdraw`, `proposals sheet`,
`proposals approve`. The same service the `/review` page calls, so a file loaded here and approved
there, or the other way round, is one record.

Sources of truth: this module, `services/proposals.py`, `docs/REVIEW-PROCESS.md` §6 (the file and
these commands in the order a review runs them), `docs/RUNBOOK.md`.
"""

from __future__ import annotations

import argparse
import json

from app.db import SessionLocal
from app.services import proposals as review
from app.services.decisions.writer import Refused


def _refused(command: str, exc: Refused) -> int:
    print(f"proposals {command}: {exc.code}: {exc.summary}")
    return 2


def cmd_load(args: argparse.Namespace) -> int:
    with SessionLocal() as db:
        try:
            counts = review.load(db, args.file, check=args.check, as_user=args.as_user, partial=args.partial)
        except review.FileInvalid as exc:
            print(f"proposals load: FILE_INVALID: {exc}")
            return 2
        except Refused as exc:
            return _refused("load", exc)
    if counts.get("unchanged"):
        print(f"proposals load: pass {counts['pass']} already holds this exact file; nothing changed")
        return 0
    shown = {k: v for k, v in counts.items() if k not in ("pass", "brand", "check")}
    print(f"proposals load{' --check (rolled back)' if args.check else ''}: pass {counts['pass']}, brand {counts['brand']}: "
          + ", ".join(f"{k} {v}" for k, v in sorted(shown.items())))
    return 0


def cmd_withdraw(args: argparse.Namespace) -> int:
    with SessionLocal() as db:
        try:
            result = review.withdraw(db, args.pass_name, args.reason, args.by)
        except Refused as exc:
            return _refused("withdraw", exc)
    print(f"proposals withdraw: pass {result['pass']}: {result['withdrawn_rows']} waiting row(s) withdrawn; "
          f"{len(result['approved_batches'])} batch(es) approved rows of it (the ledger is untouched):")
    for b in result["approved_batches"]:
        print(f"  batch {b['uid']} by {b['by']} at {b['opened_at']}: " + (f"already undone by {b['undone_by']}" if b["undone_by"] else b["undo"]))
    return 0


def cmd_sheet(args: argparse.Namespace) -> int:
    with SessionLocal() as db:
        try:
            if not args.brand:
                for row in review.sheets(db):
                    print(f"{row['brand_slug']}: open {row['open']}, spot-check {row['spot_check']}, held {row['held']}, parked {row['parked']}, "
                          f"stale {row['stale']}, gain {row['comparison_gain']}, listings {row['listings']}, newest {row['newest_pass']}")
                return 0
            data = review.sheet(db, args.brand, args.pass_name)
        except Refused as exc:
            return _refused("sheet", exc)
    print(f"sheet {data['brand']['slug']} pass {data['pass_name']}: " + ", ".join(f"{k} {v}" for k, v in sorted(data["counts"].items())))
    for group in data["groups"]:
        print(f"  [{group['ref'] or 'brand'}] {group['name'] or ''} ({len(group['rows'])} row(s), absorbs {len(group['absorbs'])})")
        if args.rows:
            for row in group["rows"]:
                mark = "*" if row["spot_check"] else " "
                eff = row["effective"]
                print(f"   {mark} {row['uid']} {row['status']:<8} {row['field']:<16} {json.dumps(row['proposed'])[:60]:<60} "
                      f"now {json.dumps(row['current_value'])[:40]}" + (f" | decided by {eff['by']} ({eff['mode']})" if eff else ""))
    for b in data["batches"]:
        print(f"  batch {b['uid']} {b['mode']} by {b['by']}: {b['decisions']} decision(s), held {b['held']}, refused {b['refused']}"
              + (" (undone)" if b["undone"] else ""))
    return 0


def cmd_approve(args: argparse.Namespace) -> int:
    chosen = [bool(args.all), bool(args.line), bool(args.uids)]
    if sum(chosen) > 1:
        print("proposals approve: choose one of --all, --line or --uids")
        return 2
    scope = "all" if args.all else ({"sheet_line_ref": args.line} if args.line else ({"proposal_uids": args.uids} if args.uids else None))
    corrections = {}
    for item in args.correct or []:
        uid, _, raw = item.partition("=")
        try:
            corrections[uid] = json.loads(raw)
        except json.JSONDecodeError:
            print(f"proposals approve: --correct {uid}=<json>, got {raw!r}")
            return 2
    if scope is None and not corrections and not args.reject:
        print("proposals approve: nothing selected (--all, --line, --uids, --correct or --reject)")
        return 2
    with SessionLocal() as db:
        try:
            result = review.approve(db, args.brand, args.pass_name, scope=scope, by=args.by, corrections=corrections,
                                    reject=args.reject, note=args.note, force=args.force)
        except Refused as exc:
            return _refused("approve", exc)
    print(f"proposals approve: {result['message'].upper()}")
    print(f"  batch {result['batch_uid']} ({result['mode']})" if result["batch_uid"] else "  no batch (nothing was recorded)")
    for item in result["refusals"] + result["held"]:
        print(f"  {item['code']}: {item['uid']} {item['natural_key']} {item['field']}: {item['summary']}")
    for item in result["absorbs"]:
        print(f"  absorb {item['line']}: {item['state']} ({item['detail']})")
    if result["undo"]:
        print(f"  undo: {result['undo']}")
    return 0


def register(sub: argparse._SubParsersAction) -> None:
    proposals = sub.add_parser("proposals", help="the review: load a proposals file, read a sheet, approve, withdraw a pass")
    psub = proposals.add_subparsers(dest="proposals_command", required=True)

    ld = psub.add_parser("load", help="load one brand's proposals file under its pass (idempotent by content)")
    ld.add_argument("--file", required=True)
    ld.add_argument("--check", action="store_true", help="resolve and count in a transaction rolled back; every table unchanged")
    ld.add_argument("--as", dest="as_user", help="the account recorded as loading it")
    ld.add_argument("--partial", action="store_true", help="the file is part of the pass: rows missing from it do not go stale")
    ld.set_defaults(func=cmd_load)

    wd = psub.add_parser("withdraw", help="withdraw a pass: waiting rows withdrawn, approved batches listed with their undo")
    wd.add_argument("--pass", dest="pass_name", required=True)
    wd.add_argument("--reason", required=True)
    wd.add_argument("--by", required=True, help="the account username")
    wd.set_defaults(func=cmd_withdraw)

    sh = psub.add_parser("sheet", help="read-only: the sheets in value order, or one brand's sheet")
    sh.add_argument("--brand")
    sh.add_argument("--pass", dest="pass_name")
    sh.add_argument("--rows", action="store_true", help="print every row")
    sh.set_defaults(func=cmd_sheet)

    ap = psub.add_parser("approve", help="approve a brand's proposals as one batch; reject rows (a pair's reject is Keep separate)")
    ap.add_argument("--brand", required=True)
    ap.add_argument("--pass", dest="pass_name", required=True)
    ap.add_argument("--all", action="store_true", help="every waiting row except spot-checks, parked, stale and held rows")
    ap.add_argument("--line", help="one product line's rows (its sheet_line_ref), spot-checks excepted")
    ap.add_argument("--uids", nargs="+", help="exactly these proposals, spot-checks included (an individual approval)")
    ap.add_argument("--correct", action="append", metavar="UID=JSON", help="approve this proposal with a corrected value")
    ap.add_argument("--reject", nargs="+", metavar="UID", help="reject these proposals (needs --note)")
    ap.add_argument("--note", help="one sentence; required with --reject")
    ap.add_argument("--by", required=True, help="the account username")
    ap.add_argument("--force", action="store_true", help="run even while a collection is running")
    ap.set_defaults(func=cmd_approve)
