#!/usr/bin/env python3
"""Install one pinned Hugo release into a local bin directory."""

from __future__ import annotations

import argparse
import json
import platform
import shutil
import stat
import tarfile
import tempfile
import urllib.request
import zipfile
from pathlib import Path
from typing import Any

_RELEASE_API_TEMPLATE = "https://api.github.com/repos/gohugoio/hugo/releases/tags/v{version}"


def _parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--version", required=True, help="Hugo version without the leading v.")
    parser.add_argument(
        "--extended",
        action="store_true",
        help="Install the extended Hugo build.",
    )
    parser.add_argument(
        "--bin-dir",
        required=True,
        help="Directory where the extracted Hugo binary should be written.",
    )
    parser.add_argument(
        "--github-path-file",
        default=None,
        help="Optional path to the GitHub Actions PATH file.",
    )
    return parser.parse_args()


def _normalized_system(value: str | None = None) -> str:
    system = (value or platform.system()).strip().lower()
    if system == "darwin":
        return "darwin"
    if system == "linux":
        return "linux"
    if system.startswith("win"):
        return "windows"
    raise ValueError(f"Unsupported operating system for Hugo install: {value!r}.")


def _normalized_machine(value: str | None = None) -> str:
    machine = (value or platform.machine()).strip().lower()
    aliases = {
        "x86_64": "amd64",
        "amd64": "amd64",
        "aarch64": "arm64",
        "arm64": "arm64",
    }
    try:
        return aliases[machine]
    except KeyError as exc:
        raise ValueError(f"Unsupported architecture for Hugo install: {value!r}.") from exc


def _candidate_asset_names(
    *,
    version: str,
    system: str | None = None,
    machine: str | None = None,
    extended: bool,
) -> tuple[str, ...]:
    normalized_system = _normalized_system(system)
    normalized_machine = _normalized_machine(machine)
    prefix = "hugo_extended" if extended else "hugo"
    if normalized_system == "windows":
        extension = ".zip"
    else:
        extension = ".tar.gz"

    platform_tokens: list[str]
    if normalized_system == "linux" and normalized_machine == "amd64":
        platform_tokens = ["linux-amd64", "Linux-64bit"]
    elif normalized_system == "linux" and normalized_machine == "arm64":
        platform_tokens = ["linux-arm64"]
    elif normalized_system == "darwin":
        platform_tokens = ["darwin-universal", f"darwin-{normalized_machine}"]
    elif normalized_system == "windows":
        platform_tokens = [f"windows-{normalized_machine}"]
    else:
        platform_tokens = [f"{normalized_system}-{normalized_machine}"]

    return tuple(f"{prefix}_{version}_{token}{extension}" for token in platform_tokens)


def _fetch_release(version: str) -> dict[str, Any]:
    url = _RELEASE_API_TEMPLATE.format(version=str(version).strip())
    request = urllib.request.Request(
        url,
        headers={
            "Accept": "application/vnd.github+json",
            "User-Agent": "dagzoo-ci-install-hugo",
        },
    )
    with urllib.request.urlopen(request) as response:
        payload = json.load(response)
    if not isinstance(payload, dict):
        raise RuntimeError(f"Unexpected release payload shape for Hugo {version!r}.")
    return payload


def _select_asset_download_url(
    release_payload: dict[str, Any],
    *,
    version: str,
    system: str | None = None,
    machine: str | None = None,
    extended: bool,
) -> tuple[str, str]:
    assets = release_payload.get("assets")
    if not isinstance(assets, list):
        raise RuntimeError(f"Hugo release payload for {version!r} did not include assets.")
    by_name = {
        str(asset.get("name")): str(asset.get("browser_download_url"))
        for asset in assets
        if isinstance(asset, dict)
        and isinstance(asset.get("name"), str)
        and isinstance(asset.get("browser_download_url"), str)
    }
    for candidate in _candidate_asset_names(
        version=version,
        system=system,
        machine=machine,
        extended=extended,
    ):
        url = by_name.get(candidate)
        if url:
            return candidate, url
    expected = ", ".join(
        _candidate_asset_names(
            version=version,
            system=system,
            machine=machine,
            extended=extended,
        )
    )
    available = ", ".join(sorted(by_name))
    raise RuntimeError(
        f"Unable to find a matching Hugo asset for version {version!r}. "
        f"Expected one of: {expected}. Available assets: {available}."
    )


def _download_file(url: str, destination: Path) -> None:
    request = urllib.request.Request(url, headers={"User-Agent": "dagzoo-ci-install-hugo"})
    with urllib.request.urlopen(request) as response, destination.open("wb") as handle:
        shutil.copyfileobj(response, handle)


def _extract_hugo_binary(archive_path: Path, *, out_dir: Path) -> Path:
    out_dir.mkdir(parents=True, exist_ok=True)
    binary_name = "hugo.exe" if archive_path.suffix == ".zip" else "hugo"
    destination = out_dir / binary_name

    if archive_path.suffix == ".zip":
        with zipfile.ZipFile(archive_path) as archive:
            for member in archive.infolist():
                if Path(member.filename).name != binary_name:
                    continue
                with archive.open(member) as src, destination.open("wb") as dst:
                    shutil.copyfileobj(src, dst)
                break
            else:
                raise RuntimeError(f"No {binary_name!r} binary found in {archive_path.name}.")
    else:
        with tarfile.open(archive_path, mode="r:*") as archive:
            for member in archive.getmembers():
                if Path(member.name).name != binary_name:
                    continue
                extracted = archive.extractfile(member)
                if extracted is None:
                    raise RuntimeError(
                        f"Unable to extract {binary_name!r} from {archive_path.name}."
                    )
                with extracted, destination.open("wb") as dst:
                    shutil.copyfileobj(extracted, dst)
                break
            else:
                raise RuntimeError(f"No {binary_name!r} binary found in {archive_path.name}.")

    current_mode = destination.stat().st_mode
    destination.chmod(current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
    return destination


def main() -> int:
    args = _parse_args()
    version = str(args.version).strip().removeprefix("v")
    bin_dir = Path(args.bin_dir).resolve()

    release_payload = _fetch_release(version)
    asset_name, download_url = _select_asset_download_url(
        release_payload,
        version=version,
        extended=bool(args.extended),
    )

    with tempfile.TemporaryDirectory(prefix="dagzoo_hugo_") as temp_dir:
        temp_root = Path(temp_dir)
        archive_path = temp_root / asset_name
        _download_file(download_url, archive_path)
        binary_path = _extract_hugo_binary(archive_path, out_dir=bin_dir)

    github_path_file = str(args.github_path_file) if args.github_path_file else None
    if github_path_file is not None:
        with Path(github_path_file).open("a", encoding="utf-8") as handle:
            handle.write(f"{bin_dir}\n")
    print(f"Installed Hugo {version} to {binary_path}")
    return 0


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