#!/usr/bin/env python3
"""Build the client-ready workbook for a run, on demand.

    python3 worker/make_report.py <RUN_ID>

Writes <exports>/<RUN_ID>_client.xlsx from the run's detailed output. Called by
download.php when the file is missing, so a run estimated before the formatted
export existed still yields a presentable workbook rather than a raw dump.

Idempotent: if the file is already newer than the detailed workbook it is left
alone.
"""
from __future__ import annotations

import json
import sys
from pathlib import Path

BASE = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(BASE))
_vendor = BASE / "vendor"
if _vendor.is_dir():
    sys.path.insert(0, str(_vendor))

import pandas as pd  # noqa: E402

from engine.excel_report import write_report  # noqa: E402
from engine.vr_report import write_vr  # noqa: E402
from engine.reference import ReferenceData  # noqa: E402


def build(run_id: str, title: str | None = None, kind: str = "client") -> Path:
    cfg = json.loads((BASE / "worker" / "config.json").read_text())
    exports = Path(cfg["exports_dir"])
    detailed = exports / f"{run_id}_detailed.xlsx"
    out = exports / f"{run_id}_{kind}.xlsx"
    if not detailed.exists():
        raise FileNotFoundError(f"no detailed workbook for {run_id}")
    # Rebuild when the DATA is newer -- and also when the CODE is. Keying the
    # cache on the detailed workbook alone means a change to the report layout
    # or palette silently serves the previous build, which has already cost two
    # rounds of diagnosing a "fix that did not work" on an artefact that was
    # never regenerated.
    newest_src = max(p.stat().st_mtime for p in [detailed] + [
        BASE / "engine" / n for n in ("vr_report.py", "vr_style.py", "excel_report.py")
        if (BASE / "engine" / n).exists()])
    if out.exists() and out.stat().st_mtime >= newest_src:
        return out

    df = pd.read_excel(detailed)
    if "flag" in df.columns:
        df["flag"] = df["flag"].fillna("")

    # The Method sheet quotes the calibration, so load it if the snapshot is
    # there. A missing snapshot must not stop the download -- the sheet then
    # reports the fallback constants, which is still the truth.
    ref = None
    try:
        fx = cfg.get("fixtures_dir")
        if fx and Path(fx).exists():
            import pandas as _pd
            frames = {}
            for name in ("ott_multipliers", "pan_mapping", "pan_channels"):
                fp = Path(fx) / f"{name}.parquet"
                if fp.exists():
                    frames[name] = _pd.read_parquet(fp)
            ref = ReferenceData.from_frames(frames)
            ref.load_calibration(Path(fx) / "calibration.json")
    except Exception:                       # noqa: BLE001
        ref = None

    if kind == "vr":
        write_vr(df, out, run_id, title or run_id, ref)
    else:
        write_report(df, out, run_id, title or run_id, ref)
    return out


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(__doc__)
        raise SystemExit(2)
    p = build(sys.argv[1],
              sys.argv[2] if len(sys.argv) > 2 else None,
              sys.argv[3] if len(sys.argv) > 3 else "client")
    print(f"wrote {p} ({p.stat().st_size:,} bytes)")
