#!/usr/bin/env python3
"""Create the exact fat adapter archive with portable, explicit member modes."""

from __future__ import annotations

import gzip
import os
from pathlib import Path
import sys
import tarfile


def add_member(
    archive: tarfile.TarFile,
    source: Path,
    arcname: str,
    mode: int,
    epoch: int,
) -> None:
    info = archive.gettarinfo(str(source), arcname=arcname)
    info.mode = mode
    info.mtime = epoch
    info.uid = 0
    info.gid = 0
    info.uname = ""
    info.gname = ""
    if source.is_dir():
        archive.addfile(info)
    else:
        with source.open("rb") as payload:
            archive.addfile(info, payload)


def main() -> int:
    if len(sys.argv) != 6:
        print(
            "usage: create-adapter-archive.py <stage> <output> "
            "<windows-triple> <gnu-triple> <musl-triple>",
            file=sys.stderr,
        )
        return 2

    stage = Path(sys.argv[1])
    output = Path(sys.argv[2])
    windows_triple, gnu_triple, musl_triple = sys.argv[3:6]
    expected_triples = (
        "x86_64-pc-windows-msvc",
        "x86_64-unknown-linux-gnu",
        "x86_64-unknown-linux-musl",
    )
    if (windows_triple, gnu_triple, musl_triple) != expected_triples:
        print(
            "release target labels must be exactly " + ", ".join(expected_triples),
            file=sys.stderr,
        )
        return 1

    try:
        epoch = int(os.environ.get("SOURCE_DATE_EPOCH", "0"), 10)
    except ValueError as error:
        print(f"invalid SOURCE_DATE_EPOCH: {error}", file=sys.stderr)
        return 2
    if epoch < 0:
        print("SOURCE_DATE_EPOCH must be non-negative", file=sys.stderr)
        return 2

    members = [
        (stage / "manifest.toml", "manifest.toml", 0o644),
        (stage / "strings", "strings", 0o755),
        (stage / "strings" / "omp-spt.mjs", "strings/omp-spt.mjs", 0o644),
        (stage / "strings" / "package.json", "strings/package.json", 0o644),
        (stage / "strings" / "skills", "strings/skills", 0o755),
        (stage / "strings" / "skills" / "commune", "strings/skills/commune", 0o755),
        (
            stage / "strings" / "skills" / "commune" / "SKILL.md",
            "strings/skills/commune/SKILL.md",
            0o644,
        ),
        (stage / "strings" / "skills" / "knock", "strings/skills/knock", 0o755),
        (
            stage / "strings" / "skills" / "knock" / "SKILL.md",
            "strings/skills/knock/SKILL.md",
            0o644,
        ),
        (stage / "strings" / "skills" / "role", "strings/skills/role", 0o755),
        (
            stage / "strings" / "skills" / "role" / "SKILL.md",
            "strings/skills/role/SKILL.md",
            0o644,
        ),
        (stage / "strings" / "skills" / "setup", "strings/skills/setup", 0o755),
        (
            stage / "strings" / "skills" / "setup" / "SKILL.md",
            "strings/skills/setup/SKILL.md",
            0o644,
        ),
        (stage / "strings" / "skills" / "signoff", "strings/skills/signoff", 0o755),
        (
            stage / "strings" / "skills" / "signoff" / "SKILL.md",
            "strings/skills/signoff/SKILL.md",
            0o644,
        ),
        (stage / windows_triple, windows_triple, 0o755),
        (
            stage / windows_triple / "omp-spt.exe",
            f"{windows_triple}/omp-spt.exe",
            0o755,
        ),
        (stage / gnu_triple, gnu_triple, 0o755),
        (stage / gnu_triple / "omp-spt", f"{gnu_triple}/omp-spt", 0o755),
        (stage / musl_triple, musl_triple, 0o755),
        (stage / musl_triple / "omp-spt", f"{musl_triple}/omp-spt", 0o755),
    ]
    directories = {
        "strings",
        "strings/skills",
        "strings/skills/commune",
        "strings/skills/knock",
        "strings/skills/role",
        "strings/skills/setup",
        "strings/skills/signoff",
        windows_triple,
        gnu_triple,
        musl_triple,
    }
    missing = [
        str(source)
        for source, arcname, _ in members
        if not source.exists()
        or source.is_symlink()
        or (arcname in directories and not source.is_dir())
        or (arcname not in directories and not source.is_file())
    ]
    if missing:
        print(
            f"missing or invalid staged archive member(s): {', '.join(missing)}",
            file=sys.stderr,
        )
        return 1

    expected = {arcname for _, arcname, _ in members}
    actual = {
        candidate.relative_to(stage).as_posix()
        for candidate in stage.rglob("*")
    }
    unexpected = sorted(actual - expected)
    if unexpected:
        print(
            f"unexpected staged archive member(s): {', '.join(unexpected)}",
            file=sys.stderr,
        )
        return 1

    output.parent.mkdir(parents=True, exist_ok=True)
    with output.open("wb") as raw_archive:
        with gzip.GzipFile(
            filename="", mode="wb", fileobj=raw_archive, mtime=epoch
        ) as compressed:
            with tarfile.open(
                fileobj=compressed, mode="w", format=tarfile.PAX_FORMAT
            ) as archive:
                for source, arcname, mode in members:
                    add_member(archive, source, arcname, mode, epoch)
    return 0


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