"""Regression harness for _grow_article_downward.

Runs the grow function over the saved fixtures of EVERY cached run, with IDENTICAL
auxiliary inputs (claimed = union of OTHER articles' members on the page,
dateline_starts = None). Because old and new code receive the same inputs, a diff
of the two output snapshots isolates exactly what the code change does.

Records per article: the grown member-id set and its union bbox, tagged with the
source newspaper (from meta.json) so results can be broken down per paper.

Usage:
    python3 _regress_grow.py snapshot.json          # snapshot current code
    python3 _regress_grow.py --diff a.json b.json   # compare two snapshots
"""
import json
import re
import sys
from collections import Counter
from pathlib import Path

OUT = Path("output")


def union_bbox(member_ids, region_map):
    bs = [region_map[r]["bbox"] for r in member_ids if r in region_map]
    if not bs:
        return None
    return [min(b[0] for b in bs), min(b[1] for b in bs),
            max(b[2] for b in bs), max(b[3] for b in bs)]


def paper_of(run_dir):
    meta = run_dir / "meta.json"
    if meta.exists():
        try:
            fn = json.loads(meta.read_text()).get("filename", "")
            name = re.split(r"[_0-9]", fn)[0].strip()
            if name:
                return name
        except Exception:
            pass
    # Cached fixtures carry no meta.json; identify the paper by page-1 image height,
    # which is distinct per edition (verified against the printed masthead nameplate).
    png = run_dir / "pages" / "page_001.png"
    if png.exists():
        try:
            from PIL import Image
            h = Image.open(png).height
            if 7040 <= h <= 7060:
                return "Sakshi"
            if h in (6833, 6549):
                return "Andhra Jyothi"
            if h == 6422:
                return "Namaste Telangana"
        except Exception:
            pass
    return "unknown"


def run():
    from smart_extractor import _grow_article_downward

    runs = sorted([d for d in OUT.iterdir() if d.is_dir()],
                  key=lambda d: d.stat().st_mtime, reverse=True)
    snapshot = {}
    papers = Counter()
    for run_dir in runs:
        pages_dir = run_dir / "pages"
        if not pages_dir.exists():
            continue
        paper = paper_of(run_dir)
        for reg_path in sorted(pages_dir.glob("page_*.regions.json")):
            pol_path = reg_path.with_name(reg_path.name.replace(".regions.json",
                                                               ".political_result.json"))
            if not pol_path.exists():
                continue
            regions = json.loads(reg_path.read_text())["regions"]
            region_map = {r["id"]: r for r in regions}
            political = json.loads(pol_path.read_text()).get("political_articles", [])

            # claimed = every member of every article (mirrors the real caller's
            # global claimed set built before growth).
            all_members = set()
            for a in political:
                all_members.update(a.get("member_region_ids", []))

            for i, art in enumerate(political):
                members = art.get("member_region_ids", []) or []
                if not members:
                    continue
                # claimed for THIS article = everything claimed by OTHER articles.
                claimed = set(all_members) - set(members)
                grown, added = _grow_article_downward(
                    members, region_map, regions, claimed, dateline_starts=None
                )
                key = f"{run_dir.name}/{reg_path.stem}/art{i}"
                snapshot[key] = {
                    "paper": paper,
                    "headline": art.get("headline_english", "")[:50],
                    "in_members": sorted(members),
                    "grown_members": sorted(grown),
                    "added": sorted(added),
                    "in_bbox": union_bbox(members, region_map),
                    "grown_bbox": union_bbox(grown, region_map),
                }
                papers[paper] += 1
    print(f"  papers: {dict(papers)}")
    return snapshot


def diff(path_a, path_b):
    """Compare snapshot A (baseline/OLD) against B (NEW). Reports per-paper how many
    articles changed their grown member set, plus a sample of the changes."""
    a = json.loads(Path(path_a).read_text())
    b = json.loads(Path(path_b).read_text())
    keys = sorted(set(a) | set(b))

    per_paper_total = Counter()
    per_paper_changed = Counter()
    grew = Counter()      # NEW added regions OLD didn't
    shrank = Counter()    # NEW dropped regions OLD had
    samples = []

    for k in keys:
        ra, rb = a.get(k), b.get(k)
        paper = (rb or ra).get("paper", "unknown")
        per_paper_total[paper] += 1
        if ra is None or rb is None:
            per_paper_changed[paper] += 1
            samples.append((k, paper, "ONLY IN ONE SNAPSHOT", None, None))
            continue
        sa, sb = set(ra["grown_members"]), set(rb["grown_members"])
        if sa != sb:
            per_paper_changed[paper] += 1
            added = sorted(sb - sa)
            removed = sorted(sa - sb)
            if added:
                grew[paper] += 1
            if removed:
                shrank[paper] += 1
            samples.append((k, paper, rb["headline"], added, removed))

    print("\n=== REGRESSION DIFF (A=baseline/OLD  →  B=new) ===")
    print(f"{'paper':<18}{'articles':>10}{'changed':>9}{'grew':>7}{'shrank':>8}")
    for paper in sorted(per_paper_total):
        print(f"{paper:<18}{per_paper_total[paper]:>10}{per_paper_changed[paper]:>9}"
              f"{grew[paper]:>7}{shrank[paper]:>8}")
    tot = sum(per_paper_total.values())
    chg = sum(per_paper_changed.values())
    print(f"{'TOTAL':<18}{tot:>10}{chg:>9}{sum(grew.values()):>7}{sum(shrank.values()):>8}")

    if samples:
        print(f"\n--- {len(samples)} changed article(s) (showing up to 40) ---")
        for k, paper, hl, added, removed in samples[:40]:
            print(f"\n[{paper}] {k}")
            print(f"  headline: {hl}")
            if added:
                print(f"  + added : {added}")
            if removed:
                print(f"  - removed: {removed}")
    else:
        print("\nNo differences — grown member sets are identical across all fixtures.")
    return chg


if __name__ == "__main__":
    if len(sys.argv) >= 4 and sys.argv[1] == "--diff":
        diff(sys.argv[2], sys.argv[3])
    else:
        out_path = sys.argv[1] if len(sys.argv) > 1 else "/tmp/grow_snapshot.json"
        snap = run()
        Path(out_path).write_text(json.dumps(snap, indent=2, ensure_ascii=False))
        print(f"wrote {len(snap)} article snapshots to {out_path}")
