#!/usr/bin/env python3
"""Sign Sparkle appcast enclosures with a Google Cloud KMS Ed25519 key."""

from __future__ import annotations

import argparse
import base64
import os
import subprocess
import sys
import tempfile
import urllib.parse
import xml.etree.ElementTree as ET
from pathlib import Path


SPARKLE_NS = "http://www.andymatuschak.org/xml-namespaces/sparkle"
DEFAULT_PROJECT = "project-88c23ce9-918a-470a-b33"
DEFAULT_LOCATION = "global"
DEFAULT_KEYRING = "burrow-identity"
DEFAULT_KEY = "sparkle-ed25519"
DEFAULT_VERSION = "1"


def run(command: list[str]) -> None:
    subprocess.run(command, check=True)


def kms_sign(args: argparse.Namespace, payload: Path) -> bytes:
    with tempfile.TemporaryDirectory(prefix="burrow-sparkle-kms.") as tmp:
        signature_path = Path(tmp) / "signature.bin"
        command = [
            args.gcloud,
            "kms",
            "asymmetric-sign",
            "--location",
            args.kms_location,
            "--keyring",
            args.kms_keyring,
            "--key",
            args.kms_key,
            "--version",
            args.kms_version,
            "--input-file",
            str(payload),
            "--signature-file",
            str(signature_path),
        ]
        if args.gcloud_project:
            command.extend(["--project", args.gcloud_project])
        run(command)
        return base64.b64decode(signature_path.read_text(encoding="utf-8").strip())


def enclosure_path(enclosure: ET.Element, artifact_dir: Path) -> Path:
    url = enclosure.attrib.get("url")
    if not url:
        raise ValueError("Sparkle enclosure is missing a url attribute")
    parsed = urllib.parse.urlparse(url)
    name = Path(parsed.path).name
    if not name:
        raise ValueError(f"Sparkle enclosure URL has no file name: {url}")
    path = artifact_dir / name
    if not path.is_file():
        raise FileNotFoundError(f"Sparkle enclosure artifact not found: {path}")
    return path


def sign_appcast(args: argparse.Namespace) -> None:
    appcast = Path(args.appcast)
    artifact_dir = Path(args.artifact_dir) if args.artifact_dir else appcast.parent
    ET.register_namespace("sparkle", SPARKLE_NS)
    tree = ET.parse(appcast)
    root = tree.getroot()
    enclosures = root.findall(".//enclosure")
    if not enclosures:
        raise ValueError(f"no Sparkle enclosures found in {appcast}")

    for enclosure in enclosures:
        artifact = enclosure_path(enclosure, artifact_dir)
        signature = kms_sign(args, artifact)
        enclosure.attrib[f"{{{SPARKLE_NS}}}edSignature"] = base64.b64encode(signature).decode("ascii")

    tree.write(appcast, encoding="utf-8", xml_declaration=True)
    print(f"signed {len(enclosures)} enclosure(s) in {appcast}")


def parse_args(argv: list[str]) -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Sign Sparkle appcast enclosures with Google Cloud KMS.")
    parser.add_argument("--appcast", required=True)
    parser.add_argument("--artifact-dir")
    parser.add_argument("--gcloud-project", default=os.environ.get("GOOGLE_CLOUD_PROJECT") or os.environ.get("BURROW_GOOGLE_PROJECT_ID") or DEFAULT_PROJECT)
    parser.add_argument("--kms-location", default=os.environ.get("BURROW_KMS_LOCATION", DEFAULT_LOCATION))
    parser.add_argument("--kms-keyring", default=os.environ.get("BURROW_KMS_KEYRING", DEFAULT_KEYRING))
    parser.add_argument("--kms-key", default=os.environ.get("BURROW_SPARKLE_KMS_KEY", DEFAULT_KEY))
    parser.add_argument("--kms-version", default=os.environ.get("BURROW_SPARKLE_KMS_VERSION", DEFAULT_VERSION))
    parser.add_argument("--gcloud", default=os.environ.get("GCLOUD", "gcloud"))
    return parser.parse_args(argv)


def main(argv: list[str]) -> int:
    sign_appcast(parse_args(argv))
    return 0


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