#!/usr/bin/env python3
import argparse
import re
from pathlib import Path


def require(text: str, token: str, label: str) -> None:
    if token not in text:
        raise SystemExit(f"FAIL: {label}: missing {token!r}")


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--source-root", required=True)
    args = parser.parse_args()
    root = Path(args.source_root)

    bridge_h = (root / "libfriidump/xbox_ref_bridge.h").read_text()
    bridge_c = (root / "libfriidump/xbox_ref_bridge.c").read_text()
    dumper_c = (root / "libfriidump/dumper.c").read_text()
    utils_c = (root / "libfriidump/xbox_ref/utils.c").read_text()
    friidump_c = (root / "src/friidump.c").read_text()
    msvc_rsp = (root / "msvc32_friidump.rsp").read_text()

    # Candidate18 removed the authRecovery parameter from the copied Windows
    # helper but left three callers with a stale eighth argument.  MSVC only
    # warned (C4020), so enforce the exact seven-argument contract in source
    # and make any future C4020 a production-build error.
    call_matches = list(
        re.finditer(r"DumpSectorRangeWithRetry\s*\((.*?)\)", utils_c, re.S)
    )
    if len(call_matches) < 2:
        raise SystemExit("FAIL: DumpSectorRangeWithRetry declaration/calls not found")
    for index, match in enumerate(call_matches[1:], start=1):
        argument_count = match.group(1).count(",") + 1
        if argument_count != 7:
            raise SystemExit(
                f"FAIL: DumpSectorRangeWithRetry call {index} has "
                f"{argument_count} arguments; expected 7"
            )
    require(msvc_rsp, "/we4020", "MSVC excess-argument warning is fatal")

    require(bridge_h, "typedef int (*xbox_ref_cancel_func)", "bridge callback type")
    require(bridge_h, "int cancelled;", "result cancellation state")
    require(bridge_h, "uint32_t completed_sectors;", "result partial progress")
    require(bridge_c, "cancel, cancel_data, result", "bridge callback handoff")
    require(dumper_c, "dumper_xbox_ref_cancel_requested", "dumper adapter")
    if dumper_c.count("dumper_xbox_ref_cancel_requested, dmp") < 2:
        raise SystemExit("FAIL: callback is not passed to both copied Xbox ISO and XISO paths")
    if utils_c.count("XboxDumpCancelRequested()") < 5:
        raise SystemExit("FAIL: copied Xbox long-running paths do not contain enough cancellation checks")
    require(utils_c, "result->completed_sectors =", "partial sector finalization")
    require(
        utils_c,
        "[HASH] Finalizing controlled partial Xbox output hashes",
        "partial output hashing",
    )
    require(
        utils_c,
        "result->cancelled &&",
        "partial hash cancellation guard",
    )
    require(
        utils_c,
        "CalculateFileHashes(filename",
        "partial full-file hash calculation",
    )
    require(utils_c, "g_xbox_dump_cancel = NULL;", "callback cleanup")
    require(friidump_c, "if (xbox_result.cancelled)", "native-report cancellation merge")
    require(
        friidump_c,
        "g_validation_summary.expected_output_sectors",
        "Xbox expected-output geometry summary",
    )
    require(
        friidump_c,
        "g_validation_summary.source_sectors",
        "Xbox source geometry summary",
    )
    require(
        friidump_c,
        "g_validation_summary.region",
        "Xbox region summary",
    )
    require(
        friidump_c,
        "xbox_result.title",
        "Xbox title summary handoff",
    )
    require(
        friidump_c,
        "xbox_result.media_id",
        "Xbox media-ID summary handoff",
    )

    print("Xbox copied-dumper cancellation, hashing, and summary contract: PASS")
    return 0


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