#!/usr/bin/env python3
"""Turn the latest audit snapshot into the human review's checklist (build plan §7, Mon 21 Sep).

Sources of truth: .logs/verification/audit-*.json (written by `app.cli audit`). Pure file
work, no database, no network. The output is a Markdown file the reviewer ticks through in
an editor, one line per page to open, with the retailer link and what to compare.

    python3 main/scripts/review-checklist.py [--snapshot PATH] [--out PATH] [--pages 30]
"""
import argparse
import json
import pathlib
import sys

ROOT = pathlib.Path(__file__).resolve().parents[2]
VERIFICATION = ROOT / ".logs" / "verification"


def latest_snapshot() -> pathlib.Path | None:
    files = sorted(VERIFICATION.glob("audit-*.json"))
    return files[-1] if files else None


def row_line(r: dict, ask: str) -> str:
    price = f"{r.get('price')} {r.get('currency')}"
    brand, name = r.get("brand") or "", r.get("name") or ""
    if brand and not name.lower().startswith(brand.lower()):
        name = f"{brand} {name}"
    size = f" {r['quantity_ml']} ml" if r.get("quantity_ml") else ""
    return f"- [ ] **{r.get('shop') or r.get('source')}** {name}{size}: we show {price}, seen {str(r.get('seen', ''))[:10]}. {ask} <{r.get('url') or 'no url'}>"


def build(snapshot: dict, pages: int) -> str:
    m, lists = snapshot["metrics"], snapshot["lists"]
    out = [
        f"# Human review checklist, generated from the audit of {snapshot['taken_at'][:10]}",
        "<!-- Role: the Mon 21 Sep review's worksheet. Tick a line when the page has been opened",
        "     and compared. Write the finding on the line. Dated; not maintained after the day. -->",
        "",
        "Method: open the retailer page, compare price, size, currency and product against what we",
        "show. A wrong FIELD (the price of another size, a crossed-out price, a different currency)",
        "is what this review exists to catch; verify cannot see it. Record the date beside each tick.",
        "",
        "## 1. The pages that must be opened (correctness)",
        "",
    ]
    seen_urls: set[str] = set()
    count = 0
    for r in m["oversize_singles"]["sample"]:
        out.append(f"- [ ] **size {r['quantity_ml']} ml** {r['name']}: is this a real large format or a misread? <{r.get('url') or 'no url'}>")
    for r in m["cross_shop_ratio"]["sample"][:10]:
        c, d = r["cheapest"], r["dearest"]
        out.append(f"- [ ] **spread {r['ratio']}x** {r['name']}: {c['shop']} {c['price']} {c['currency']} <{c.get('url')}> vs {d['shop']} {d['price']} {d['currency']} <{d.get('url')}>. Which side is wrong, if either?")
    for r in lists["largest_discounts"][:5]:
        out.append(row_line(r, f"Promotion of {r['discount_pct']}% (was {r.get('was_price')}): is the crossed-out figure real?"))
    for r in m["same_day_flips"]["sample"]:
        out.append(f"- [ ] **same-day flip** listing {r['listing_id']} on {r['day']}: {r['prices']}. Which one does the page show?")
    for r in m["currency_mismatch"]["sample"][:5]:
        out.append(row_line(r, f"Shop says {r.get('shop')}; observation currency {r.get('currency')}."))
    out += ["", f"## 2. Sample rows per shop (pick to reach {pages} pages in total)", ""]
    for slug, rows in lists["sample_rows"].items():
        out.append(f"### {slug}")
        for r in rows:
            if r.get("url") in seen_urls:
                continue
            seen_urls.add(r.get("url"))
            out.append(row_line(r, "Price, size, currency, product: all four agree?"))
            count += 1
        out.append("")
    out += ["## 3. Brand spellings the fold has not joined (object to a wrong fold; none is the expected answer)", ""]
    for f in lists["brand_folds"][:40]:
        out.append(f"- [ ] `{f['key']}`: " + ", ".join(f"{s} ({n})" for s, n in f["spellings"].items()))
    out += ["", "## 4. Feeds that never say out of stock", ""]
    for r in m["stock_smell_sources"]["sample"]:
        out.append(f"- [ ] {r['source']}: open three product pages; does the site itself ever show sold out?")
    out += ["", "## 5. Duplicate groups the merge rules would fold (none is the expected answer: backfill merges folds them)", ""]
    for g in m["duplicate_groups"]["sample"][:20]:
        names = "; ".join(f"{p['name']} (id {p['id']}, gtin {p.get('gtin') or '-'})" for p in g["product_variants"])
        out.append(f"- [ ] `{g['kind']}` {names}")
    # The rules queue what they will not settle alone (two barcodes under one key, a set
    # against its bottle) in suggestions; the reviewer says which is the right product.
    out += ["", "## 6. Merge candidates awaiting a decision: one product or two?", ""]
    for c in lists.get("suggestions", [])[:40]:
        names = "; ".join(f"{p.get('name')} (id {p.get('id')}, gtin {p.get('gtin') or '-'})" for p in c["product_variants"])
        out.append(f"- [ ] `{c['reason']}` {names}")
    out += ["", "## Findings", "", "_Date, reviewer, what was wrong, what was done._", ""]
    return "\n".join(out) + "\n"


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--snapshot", help="audit JSON (default: the newest in .logs/verification/)")
    ap.add_argument("--out", help="Markdown path (default .logs/verification/review-checklist-<date>.md)")
    ap.add_argument("--pages", type=int, default=30)
    a = ap.parse_args()
    path = pathlib.Path(a.snapshot) if a.snapshot else latest_snapshot()
    if path is None or not path.is_file():
        print("no audit snapshot found; run: python -m app.cli audit", file=sys.stderr)
        return 2
    snapshot = json.loads(path.read_text())
    out = pathlib.Path(a.out) if a.out else VERIFICATION / f"review-checklist-{snapshot['taken_at'][:10]}.md"
    out.write_text(build(snapshot, a.pages))
    print(f"{out} ({sum(1 for l in out.read_text().splitlines() if l.startswith('- [ ]'))} lines to tick)")
    return 0


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