"""Parser cross-validation — run a real Clockify CSV through BOTH
parsers (the legacy TypeScript code of record and this app's Python
port) and diff field-by-field. The M4 standing check: every archived
weekly CSV (harness/csv-archive/) must diff EMPTY until cutover
(09-build-plan M4).

Usage (host, from /srv/apps/with):
    .venv/bin/python -m scripts.cross_validate_csv harness/csv-archive/*.csv
    .venv/bin/python -m scripts.cross_validate_csv --ts-json out.json some.csv

The TS side runs via the old app's tsx runner:
    /srv/apps/work/node_modules/.bin/tsx scripts/ts_parse_clockify.mjs <csv>
If node/tsx is unavailable (e.g. inside the app container), produce the
oracle JSON on a machine that has it and pass --ts-json.

Known-and-accepted diffs (the port's DELIBERATE deltas — ADR #005) are
normalized away before comparison:
  - field names: source_rate_usd/source_amount_usd -> source_rate/source_amount
  - the Python side adds `warnings` (invalid END surfacing) — ignored here
"""

from __future__ import annotations

import argparse
import json
import subprocess
import sys
from pathlib import Path

from app.services.imports.clockify import format_date_range_label, parse_clockify_csv

TSX = Path("/srv/apps/work/node_modules/.bin/tsx")
TS_RUNNER = Path(__file__).resolve().parent / "ts_parse_clockify.mjs"

MANUAL_ORACLE_HELP = f"""\
Cannot run the TS oracle ({TSX} not found or failed).
Manual path: on a machine with node + the old app checked out, run
    {TSX} {TS_RUNNER} <csv> > oracle.json
then re-run this script with --ts-json oracle.json <csv>."""


def python_side(csv_path: Path) -> dict:
    result = parse_clockify_csv(csv_path.read_text(encoding="utf-8-sig"))
    return {
        "entries": [e.as_dict() for e in result.entries],
        "errors": result.errors,
        "totalRows": result.total_rows,
        "uniqueEmails": result.unique_emails,
        "dateRange": result.date_range,
        "dateRangeLabel": format_date_range_label(result.date_range),
    }


def ts_side(csv_path: Path) -> dict:
    proc = subprocess.run(
        [str(TSX), str(TS_RUNNER), str(csv_path)],
        capture_output=True,
        text=True,
        timeout=120,
    )
    if proc.returncode != 0:
        raise RuntimeError(f"tsx runner failed:\n{proc.stderr}")
    return json.loads(proc.stdout)


def normalize_ts_entry(entry: dict) -> dict:
    """Map the legacy field names onto the port's (ADR #005 delta)."""
    out = dict(entry)
    out["source_rate"] = out.pop("source_rate_usd", None)
    out["source_amount"] = out.pop("source_amount_usd", None)
    return out


def diff(ts: dict, py: dict) -> list[str]:
    problems: list[str] = []
    for scalar in ("totalRows", "uniqueEmails", "dateRange", "dateRangeLabel"):
        if ts.get(scalar) != py.get(scalar):
            problems.append(f"{scalar}: ts={ts.get(scalar)!r} py={py.get(scalar)!r}")
    ts_errors = ts.get("errors", [])
    py_errors = py.get("errors", [])
    if ts_errors != py_errors:
        problems.append(f"errors: ts={ts_errors!r} py={py_errors!r}")

    ts_entries = [normalize_ts_entry(e) for e in ts.get("entries", [])]
    py_entries = py.get("entries", [])
    if len(ts_entries) != len(py_entries):
        problems.append(f"entry count: ts={len(ts_entries)} py={len(py_entries)}")
        return problems
    for i, (ts_entry, py_entry) in enumerate(zip(ts_entries, py_entries, strict=True)):
        keys = sorted(set(ts_entry) | set(py_entry))
        for key in keys:
            if ts_entry.get(key) != py_entry.get(key):
                problems.append(
                    f"entry[{i}].{key}: ts={ts_entry.get(key)!r} py={py_entry.get(key)!r}"
                )
    return problems


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("csvs", nargs="+", type=Path)
    parser.add_argument(
        "--ts-json",
        type=Path,
        default=None,
        help="pre-produced TS oracle JSON (single-CSV mode; skips tsx)",
    )
    args = parser.parse_args()

    if args.ts_json and len(args.csvs) != 1:
        parser.error("--ts-json compares exactly one CSV")

    failures = 0
    for csv_path in args.csvs:
        py = python_side(csv_path)
        if args.ts_json:
            ts = json.loads(args.ts_json.read_text())
        else:
            if not TSX.exists():
                print(MANUAL_ORACLE_HELP, file=sys.stderr)
                return 2
            try:
                ts = ts_side(csv_path)
            except Exception as exc:
                print(f"{csv_path}: TS oracle failed: {exc}", file=sys.stderr)
                print(MANUAL_ORACLE_HELP, file=sys.stderr)
                return 2
        problems = diff(ts, py)
        if problems:
            failures += 1
            print(f"✗ {csv_path}: {len(problems)} field diff(s)")
            for problem in problems[:50]:
                print(f"    {problem}")
        else:
            print(
                f"✓ {csv_path}: identical ({len(py['entries'])} entries,"
                f" {py['totalRows']} rows, range {py['dateRangeLabel']})"
            )
    return 1 if failures else 0


if __name__ == "__main__":
    raise SystemExit(main())
