#!/usr/bin/env python3
"""Run a small GDR-8050L virtual media sequence.

This is a convenience wrapper around run_direct_firmware.py.  It does not keep a
live CPU instance between commands; instead it creates a reproducible three-step
host transaction sequence that mirrors the workflow we want to model next:

  1. START STOP UNIT eject/unload
  2. mount ISO metadata and START STOP UNIT inject/load
  3. issue READ DVD STRUCTURE with the ISO attached

The trace files are separate so each packet phase remains easy to inspect.
"""
from __future__ import annotations

import argparse
import subprocess
import sys
from pathlib import Path

VERSION = "v92"


def resolve_default_bin(workspace: Path) -> Path | None:
    candidates = [
        workspace / "firmware" / "GDR-8050L" / "8050L_fw_plaintext_256k.bin",
        workspace / "GDR-8050L_0L23_5af3ae9b_firmware_4c61eb12_plaintext_7cab0f5c.bin",
    ]
    for p in candidates:
        if p.exists():
            return p
    return None


def run_step(ns: argparse.Namespace, name: str, extra: list[str]) -> int:
    script_dir = Path(__file__).resolve().parent
    runner = script_dir / "run_direct_firmware.py"
    trace_name = f"{ns.out_prefix}_{name}_v92_trace.csv"
    preset = "gdr8050l-packet-full-cdb" if name == "read_dvd_structure" else "gdr8050l-packet-continue"
    cmd = [
        ns.python,
        str(runner),
        "--workspace", str(ns.workspace),
        "--model", ns.model,
        "--bin", str(ns.bin),
        "--base", ns.base,
        "--entry", ns.entry,
        "--max-cycles", str(ns.max_cycles),
        "--trace-csv", trace_name,
        "--preset", preset,
        "--trace-summary",
        *(["--auth-watch"] if name == "read_dvd_structure" else []),
        *extra,
    ]
    print("\n[SEQ] " + name)
    print("[RUN] " + " ".join(cmd), flush=True)
    rc = subprocess.run(cmd).returncode
    if rc == 0 and name == "read_dvd_structure" and not getattr(ns, "no_extract_handshake", False):
        extractor = script_dir / "extract_gdr_handshake.py"
        trace_path = Path(trace_name)
        out_json = Path(f"{ns.out_prefix}_handshake_v92.json")
        out_all = Path(f"{ns.out_prefix}_read_dvd_structure_df70_all_v92.bin")
        out_post = Path(f"{ns.out_prefix}_read_dvd_structure_df70_after_packet_v92.bin")
        extract_cmd = [
            ns.python, str(extractor), str(trace_path),
            "--out-json", str(out_json),
            "--out-df70", str(out_all),
            "--out-df70-after-packet", str(out_post),
        ]
        print("[EXTRACT] " + " ".join(extract_cmd), flush=True)
        erc = subprocess.run(extract_cmd).returncode
        if erc != 0:
            return erc
    return rc


def main(argv: list[str]) -> int:
    ap = argparse.ArgumentParser(description="Run GDR-8050L eject -> mount ISO -> inject -> read DVD structure sequence")
    ap.add_argument("--workspace", required=True, type=Path)
    ap.add_argument("--bin", type=Path, help="GDR-8050L plaintext firmware binary; defaults to common workspace paths")
    ap.add_argument("--iso", required=True, type=Path, help="ISO image to attach for the inject/read steps")
    ap.add_argument("--model", default="GDR-8050L")
    ap.add_argument("--base", default="0x90000000")
    ap.add_argument("--entry", default="0x90000020")
    ap.add_argument("--max-cycles", type=int, default=60000)
    ap.add_argument("--out-prefix", default="gdr8050l_media_sequence")
    ap.add_argument("--skip-eject", action="store_true")
    ap.add_argument("--skip-read-dvd-structure", action="store_true")
    ap.add_argument("--no-extract-handshake", action="store_true", help="do not run extract_gdr_handshake.py after READ DVD STRUCTURE")
    ap.add_argument("--python", default=sys.executable)
    ns = ap.parse_args(argv[1:])

    ns.workspace = ns.workspace.resolve()
    if ns.bin is None:
        default_bin = resolve_default_bin(ns.workspace)
        if default_bin is None:
            print("[-] could not find default GDR-8050L firmware binary; pass --bin", file=sys.stderr)
            return 2
        ns.bin = default_bin
    ns.bin = ns.bin.resolve()
    ns.iso = ns.iso.resolve()
    if not ns.bin.exists():
        print(f"[-] firmware binary does not exist: {ns.bin}", file=sys.stderr)
        return 2
    if not ns.iso.exists():
        print(f"[-] ISO does not exist: {ns.iso}", file=sys.stderr)
        return 2

    print(f"[SEQ] version={VERSION} iso={ns.iso} firmware={ns.bin}")
    steps: list[tuple[str, list[str]]] = []
    if not ns.skip_eject:
        steps.append(("eject", ["--tray-state", "closed", "--disc-present", "--eject-tray"]))
    steps.append(("inject", ["--tray-state", "open", "--iso", str(ns.iso), "--inject-tray"]))
    if not ns.skip_read_dvd_structure:
        steps.append(("read_dvd_structure", ["--tray-state", "closed", "--iso", str(ns.iso), "--packet-cdb", "read-dvd-structure-xbox"]))

    for name, extra in steps:
        rc = run_step(ns, name, extra)
        if rc != 0:
            print(f"[-] sequence stopped at {name} rc={rc}", file=sys.stderr)
            return rc
    print("\n[OK] media sequence complete")
    return 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv))
