#!/usr/bin/env python3
"""Create a GitHub Actions release package from Rust release binaries."""

from __future__ import annotations

import argparse
import hashlib
import json
import shutil
import stat
import tarfile
from datetime import datetime, timezone
from pathlib import Path

SKIP_DIRS = {"deps", "build", "examples", "incremental"}
SKIP_SUFFIXES = {".d", ".rlib", ".rmeta"}


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        while True:
            chunk = handle.read(1024 * 1024)
            if not chunk:
                break
            digest.update(chunk)
    return digest.hexdigest()


def is_binary(path: Path) -> bool:
    if not path.is_file():
        return False
    if path.name in SKIP_DIRS:
        return False
    if path.suffix in SKIP_SUFFIXES:
        return False
    return bool(path.stat().st_mode & stat.S_IXUSR)


def collect(release_dir: Path) -> list[Path]:
    items = [item for item in sorted(release_dir.iterdir()) if is_binary(item)]
    if not items:
        raise SystemExit(f"No release binaries found in {release_dir}")
    return items


def write(path: Path, text: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(text, encoding="utf-8")


def copy_release_file(src: Path, dst: Path) -> Path:
    """Copy file contents without preserving metadata that some mounts reject."""
    dst.parent.mkdir(parents=True, exist_ok=True)
    shutil.copyfile(src, dst)
    try:
        dst.chmod(src.stat().st_mode & 0o777)
    except PermissionError:
        # Some removable/network filesystems reject chmod/utime metadata changes.
        # The package remains valid because the archive manifest/checksums are
        # based on file contents, not filesystem timestamps.
        pass
    return dst


def write_archive_checksum(archive: Path) -> None:
    write(archive.with_suffix(archive.suffix + ".sha256"), f"{sha256(archive)}  {archive.name}\n")


def create_compatibility_aliases(out_dir: Path, archive: Path) -> None:
    """Create both linux-x86_64 and linux_x86_64 artifact paths."""
    out_alias = Path(str(out_dir).replace("linux-x86_64", "linux_x86_64"))
    if out_alias != out_dir:
        if out_alias.exists():
            shutil.rmtree(out_alias)
        out_alias.mkdir(parents=True)
        for item in out_dir.iterdir():
            if item.is_file():
                copy_release_file(item, out_alias / item.name)

    archive_alias = Path(str(archive).replace("linux-x86_64", "linux_x86_64"))
    if archive_alias != archive:
        copy_release_file(archive, archive_alias)
        write_archive_checksum(archive_alias)


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--release-dir", type=Path, required=True)
    parser.add_argument("--out-dir", type=Path, required=True)
    parser.add_argument("--archive", type=Path, required=True)
    parser.add_argument("--target", default="linux-x86_64")
    parser.add_argument("--commit", default="unknown")
    parser.add_argument("--ref", default="unknown")
    parser.add_argument("--run-id", default="unknown")
    args = parser.parse_args()

    release_dir = args.release_dir.resolve()
    out_dir = args.out_dir.resolve()
    archive = args.archive.resolve()

    if out_dir.exists():
        shutil.rmtree(out_dir)
    out_dir.mkdir(parents=True)

    binaries = collect(release_dir)
    for binary in binaries:
        copy_release_file(binary, out_dir / binary.name)

    names = [binary.name for binary in binaries]
    write(out_dir / "BINARIES.txt", "\n".join(names) + "\n")

    checksum_lines = []
    manifest_binaries = []
    for name in names:
        packaged = out_dir / name
        digest = sha256(packaged)
        checksum_lines.append(f"{digest}  {name}")
        manifest_binaries.append(
            {"name": name, "size_bytes": packaged.stat().st_size, "sha256": digest}
        )
    write(out_dir / "SHA256SUMS.txt", "\n".join(checksum_lines) + "\n")

    manifest = {
        "project": "AWatch-rus",
        "target": args.target,
        "commit": args.commit,
        "ref": args.ref,
        "run_id": args.run_id,
        "build_time_utc": datetime.now(timezone.utc).isoformat(timespec="seconds"),
        "binaries": manifest_binaries,
    }
    write(out_dir / "BUILD_MANIFEST.json", json.dumps(manifest, ensure_ascii=False, indent=2) + "\n")

    archive.parent.mkdir(parents=True, exist_ok=True)
    with tarfile.open(archive, "w:gz") as tar:
        tar.add(out_dir, arcname=out_dir.name)
    write_archive_checksum(archive)
    create_compatibility_aliases(out_dir, archive)


if __name__ == "__main__":
    main()
