"""Turn scraped eTRAKiT records into per-project timelines.

Input : etrakit/finaled_subset_raw.json  (output of etrakit_scrape.js, decoded)
Output: etrakit/projects_timeline.json, etrakit/projects_timeline.md
"""
import json, re, sys
from datetime import datetime, date
from collections import defaultdict, OrderedDict

RAW = sys.argv[1] if len(sys.argv) > 1 else "etrakit/finaled_subset_raw.json"
recs = {r["no"]: r for r in json.load(open(RAW))}

# Hand-grouped developments for this subset (permit numbers -> project label)
GROUPS = OrderedDict([
    ("Hartman small-lot homes, 1223-1229 E Laurel St (Sehome)", ["BLD2025-0258", "BLD2025-0259", "BLD2025-0260", "BLD2025-0261"]),
    ("King Mountain Phase 2 small lots, Blackstone Way (Lots 14-19)", ["BLD2025-0446", "BLD2025-0445", "BLD2025-0444", "BLD2025-0443", "BLD2025-0442", "BLD2025-0430"]),
    ("Savchuk 2-unit DADU + SFR, 3200 Meridian St", ["BLD2026-0053", "BLD2026-0052", "BLD2026-0013"]),
    ("Myers SFR + 2 DADUs, 210 Carolina St (Sunnyland)", ["BLD2025-1027", "BLD2025-1028", "BLD2025-1029"]),
    ("Hansen 2-unit DADU, 1247 Franklin St", ["BLD2025-1010"]),
    ("Jersey St LLC 2-unit DADU, 1017 Jersey St", ["BLD2025-0885"]),
    ("Garden Properties 2-unit DADU, 707 E Laurel St", ["BLD2025-0290"]),
])

def d(s):
    s = (s or "").strip()
    if not s: return None
    for fmt in ("%m/%d/%Y", "%m/%d/%Y %I:%M:%S %p"):
        try: return datetime.strptime(s, fmt).date()
        except ValueError: pass
    return None

def money(s):
    try: return float(re.sub(r"[^\d.\-]", "", s or "0") or 0)
    except ValueError: return 0.0

def review_rounds(rows):
    """Group review rows by submittal date -> rounds with city/applicant day counts."""
    by_sub = defaultdict(list)
    for r in rows:
        by_sub[d(r["Submitted"])].append(r)
    rounds = []
    prev_done = None
    for i, sub in enumerate(sorted(k for k in by_sub if k)):
        rs = by_sub[sub]
        done = [d(r["Completed"]) for r in rs if d(r["Completed"])]
        completed = max(done) if done else None
        needs = [r["Type"] for r in rs if "REVISION" in (r["Status"] or "")]
        rounds.append({
            "round": i + 1,
            "submitted": sub.isoformat(),
            "completed": completed.isoformat() if completed else None,
            "city_days": (completed - sub).days if completed else None,
            "applicant_days_before": (sub - prev_done).days if prev_done else None,
            "disciplines": [f'{r["Type"]} ({r["Reviewer"]}): {r["Status"]}' for r in rs],
            "revision_needed": needs,
            "outcome": "revisions requested" if needs else ("all approved" if all("APPROVED" in (r["Status"] or "") for r in rs) else "open"),
        })
        prev_done = completed or prev_done
    return rounds

def permit_summary(no):
    r = recs[no]; info = r["info"]; g = r["grids"]
    applied, issued, finaled = d(info.get("Applied Date")), d(info.get("Issued Date")), d(info.get("Finaled Date"))
    rounds = review_rounds(g.get("ReviewInfo", []))
    insp = g.get("InspectionInfo", [])
    fails = [x for x in insp if re.search(r"FAIL|PARTIAL|CANCEL", x.get("Result") or "")]
    fees = g.get("FeeDetails", [])
    conds = g.get("Conditions", [])
    custom = [c for c in conds if "CUSTOM" in (c.get("Type") or "")]
    parents = []
    for p in r.get("parents", []):
        pr = recs.get(p["no"])
        if pr:
            pi = pr["info"]
            parents.append({"no": p["no"], "type": pi.get("Type") or p.get("type"), "status": pi.get("Status"),
                            "applied": pi.get("Applied") or pi.get("Applied Date"), "approved": pi.get("Approved") or pi.get("Approved Date") or pi.get("Issued Date"),
                            "planner": pi.get("Planner"), "description": pi.get("Description") or pi.get("Project Description"),
                            "reviews": [f'{x["Type"]}: {x["Status"]} ({x["Submitted"]}→{x["Completed"]})' for x in pr["grids"].get("ReviewInfo", [])],
                            "attachments": [{"name": a["name"], "mb": round((a.get("bytes") or 0)/1048576, 1), "href": a["href"]} for a in pr["attachments"]]})
        else:
            parents.append({"no": p["no"], "type": p.get("type"), "status": p.get("status")})
    contacts = {c["Contact Type"]: c["Name"] for c in g.get("ContactInfo", [])}
    return {
        "permit": no, "type": info.get("Type"), "description": info.get("Project Description"), "status": info.get("Status"),
        "address": r["site"].get("Address"), "apn": r["site"].get("APN/PIN"),
        "applied": applied.isoformat() if applied else None, "issued": issued.isoformat() if issued else None, "finaled": finaled.isoformat() if finaled else None,
        "days_applied_to_issued": (issued - applied).days if applied and issued else None,
        "days_issued_to_finaled": (finaled - issued).days if issued and finaled else None,
        "review_rounds": rounds,
        "city_review_days_total": sum(x["city_days"] or 0 for x in rounds),
        "applicant_turnaround_days_total": sum(x["applicant_days_before"] or 0 for x in rounds),
        "inspections": {"count": len(insp), "not_passed": len(fails),
                        "first": min((d(x["Completed"]) for x in insp if d(x["Completed"])), default=None).isoformat() if insp else None,
                        "notable": [f'{x["Type"]}: {x["Result"]} {x["Completed"]} — {(x.get("detail",{}).get("fields",{}).get("RemarksText") or "").strip()[:200]}' for x in fails if (x.get("detail",{}).get("fields",{}).get("RemarksText") or "").strip() not in ("", "eTRAKiT Inspection Request")][:12]},
        "fees_total": round(sum(money(f["Amount"]) for f in fees), 2),
        "fees": [{"desc": f["Description"], "amount": money(f["Amount"]), "paid": f["Paid Date"]} for f in fees],
        "custom_conditions": [{"type": c["Type"], "dept": c["Department"], "status": c["Status"], "satisfied": c.get("Satisfied"), "text": (c.get("detail",{}).get("fields",{}).get("ConditionText") or "")[:400]} for c in custom],
        "contacts": contacts,
        "parents": parents,
        "children": r.get("children", []),
        "attachments": [{"name": a["name"], "mb": round((a.get("bytes") or 0)/1048576, 1), "href": a["href"]} for a in r["attachments"]],
        "url": r["url"],
    }

out = []
for label, nos in GROUPS.items():
    permits = [permit_summary(n) for n in nos if n in recs]
    iso = date.fromisoformat
    apps = [iso(p["applied"]) for p in permits if p["applied"]]
    iss = [iso(p["issued"]) for p in permits if p["issued"]]
    fin = [iso(p["finaled"]) for p in permits if p["finaled"]]
    parent_apps = [d(pp.get("applied")) for p in permits for pp in p["parents"] if d(pp.get("applied"))]
    out.append({
        "project": label, "permits": permits,
        "first_planning_application": min(parent_apps).isoformat() if parent_apps else None,
        "building_applied": min(apps).isoformat() if apps else None,
        "first_issued": min(iss).isoformat() if iss else None, "last_issued": max(iss).isoformat() if iss else None,
        "first_finaled": min(fin).isoformat() if fin else None, "last_finaled": max(fin).isoformat() if fin else None,
        "fees_total": round(sum(p["fees_total"] for p in permits), 2),
        "units": sum(2 if "AADU" in (p["description"] or "") or "TWO UNITS" in (p["description"] or "") or "(2) UNITS" in (p["description"] or "") or "DADUS" in (p["description"] or "") else 1 for p in permits if p["type"] and "DEMOLI" not in p["type"]),
    })
json.dump(out, open("etrakit/projects_timeline.json", "w"), indent=1)

# Markdown narrative
L = []
for pr in out:
    L.append(f"# {pr['project']}\n")
    L.append(f"- Planning application: {pr['first_planning_application']}  · Building applied: {pr['building_applied']}  · Issued: {pr['first_issued']} → {pr['last_issued']}  · Finaled: {pr['first_finaled']} → {pr['last_finaled']}")
    L.append(f"- Permits: {len(pr['permits'])}  · Fees paid: ${pr['fees_total']:,.0f}\n")
    for p in pr["permits"]:
        L.append(f"## {p['permit']} — {p['address']} — {p['description']}")
        L.append(f"Status {p['status']}. Applied {p['applied']}, issued {p['issued']} ({p['days_applied_to_issued']} days), finaled {p['finaled']} ({p['days_issued_to_finaled']} days of construction). Fees ${p['fees_total']:,.0f}.")
        L.append(f"Contacts: " + "; ".join(f"{k}: {v}" for k, v in p["contacts"].items()))
        for pp in p["parents"]:
            L.append(f"Parent {pp['no']} ({pp.get('type')}, {pp.get('status')}): applied {pp.get('applied')}, approved {pp.get('approved')}, planner {pp.get('planner')}. {pp.get('description') or ''}")
        L.append(f"Review rounds ({len(p['review_rounds'])}); city {p['city_review_days_total']} days, applicant {p['applicant_turnaround_days_total']} days:")
        for rd in p["review_rounds"]:
            L.append(f"  {rd['round']}. submitted {rd['submitted']} → done {rd['completed']} ({rd['city_days']}d city" + (f", after {rd['applicant_days_before']}d applicant)" if rd['applicant_days_before'] is not None else ")") + f": {rd['outcome']}" + (f" — {', '.join(rd['revision_needed'])}" if rd['revision_needed'] else ""))
        if p["custom_conditions"]:
            L.append("Custom conditions:")
            for c in p["custom_conditions"]: L.append(f"  - [{c['dept']}] {c['status']} {c['satisfied'] or ''}: {c['text'][:200]}")
        L.append(f"Inspections: {p['inspections']['count']} total, {p['inspections']['not_passed']} failed/partial/cancelled, first {p['inspections']['first']}.")
        for n in p["inspections"]["notable"]: L.append(f"  - {n}")
        L.append("Attachments: " + "; ".join(f"{a['name']} ({a['mb']} MB)" for a in p["attachments"]))
        L.append("")
open("etrakit/projects_timeline.md", "w").write("\n".join(L))
print("\n".join(L[:60]))
