#!/usr/bin/env python3

from __future__ import annotations

import argparse
import datetime as dt
import json
import hashlib
import pathlib
import platform as py_platform
import subprocess
import urllib.parse
import urllib.request
from typing import Any

try:
    import tomllib
except ModuleNotFoundError:  # pragma: no cover - python < 3.11 fallback
    import tomli as tomllib  # type: ignore


def utc_now() -> str:
    return dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def current_arch() -> str:
    machine = py_platform.machine().lower()
    if machine in {"arm64", "aarch64"}:
        return "arm64"
    if machine in {"x86_64", "amd64"}:
        return "x86_64"
    raise SystemExit(f"unsupported architecture: {py_platform.machine()}")


def run_version(command: str, installer: str, name: str) -> str:
    if installer == "official_cask":
        completed = subprocess.run(
            ["brew", "list", "--cask", "--versions", name],
            check=False,
            capture_output=True,
            text=True,
        )
        text = (completed.stdout or completed.stderr).strip()
        if text:
            return text.splitlines()[0].strip()

    if installer == "brew":
        completed = subprocess.run(
            ["brew", "list", "--versions", name],
            check=False,
            capture_output=True,
            text=True,
        )
        text = (completed.stdout or completed.stderr).strip()
        if text:
            return text.splitlines()[0].strip()

    if command == "tmux":
        args = [command, "-V"]
    else:
        args = [command, "--version"]

    completed = subprocess.run(args, check=True, capture_output=True, text=True)
    line = (completed.stdout or completed.stderr).strip().splitlines()[0]
    return line.strip()


def render_value(value: Any) -> str:
    if isinstance(value, bool):
        return "true" if value else "false"
    if value is None:
        return '""'
    if isinstance(value, (int, float)):
        return str(value)
    text = str(value)
    text = text.replace("\\", "\\\\").replace('"', '\\"')
    return f'"{text}"'


def write_table(lines: list[str], path: str, table: dict[str, Any]) -> None:
    lines.append(f"[{path}]")
    for key, value in table.items():
        if isinstance(value, dict):
            continue
        lines.append(f"{key} = {render_value(value)}")
    lines.append("")


def load_toml(path: pathlib.Path) -> dict[str, Any]:
    if not path.exists():
        return {}
    with path.open("rb") as fh:
        return tomllib.load(fh)


def derive_filename(name: str, platform_name: str, version: str, installer: str) -> str:
    if installer in {"brew", "system", "apt"}:
        return ""
    if installer == "official_script":
        return "install.sh"
    if installer == "official_cask":
        return ""
    if installer == "community_ubuntu":
        return "install.sh"
    if installer == "official_tarball" and name == "nvim" and platform_name == "linux":
        arch = current_arch()
        return f"nvim-linux-{arch}.tar.gz"
    if installer == "official_release":
        arch = current_arch()
        if name == "yazi":
            asset_arch = "aarch64" if arch == "arm64" else "x86_64"
            return f"yazi-{asset_arch}-unknown-linux-gnu.zip"
        if name == "eza":
            asset_arch = "aarch64" if arch == "arm64" else "x86_64"
            return f"eza_{asset_arch}-unknown-linux-gnu.tar.gz"
        if name == "lazygit":
            asset_arch = arch
            clean = version.lstrip("v")
            return f"lazygit_{clean}_Linux_{asset_arch}.tar.gz"
    return ""


def should_write_lock(installer: str) -> bool:
    return installer != "system"


def github_latest_tag(repo: str) -> str:
    request = urllib.request.Request(
        f"https://api.github.com/repos/{repo}/releases/latest",
        headers={"Accept": "application/vnd.github+json", "User-Agent": "codex-dotfiles-lock"},
    )
    with urllib.request.urlopen(request) as response:
        payload = json.loads(response.read().decode("utf-8"))

    tag = payload.get("tag_name", "")
    if tag:
        return str(tag)

    raise SystemExit(f"failed to resolve latest tag for {repo}")


def resolve_artifact_url(name: str, platform_name: str, target: str, version: str, installer: str, source_url: str) -> str:
    source_path = pathlib.Path(urllib.parse.unquote(urllib.parse.urlparse(source_url).path))
    if source_url.startswith("file://") or source_path.exists():
        return source_url

    if installer == "official_tarball" and name == "nvim" and platform_name == "linux":
        arch = current_arch()
        if target == "latest":
            return f"https://github.com/neovim/neovim/releases/latest/download/nvim-linux-{arch}.tar.gz"
        tag = target
        return f"https://github.com/neovim/neovim/releases/download/{tag}/nvim-linux-{arch}.tar.gz"

    if installer == "official_release":
        arch = current_arch()
        if name == "yazi":
            tag = target if target != "latest" else github_latest_tag("sxyazi/yazi")
            asset_arch = "aarch64" if arch == "arm64" else "x86_64"
            return f"https://github.com/sxyazi/yazi/releases/download/{tag}/yazi-{asset_arch}-unknown-linux-gnu.zip"
        if name == "eza":
            tag = target if target != "latest" else github_latest_tag("eza-community/eza")
            asset_arch = "aarch64" if arch == "arm64" else "x86_64"
            return f"https://github.com/eza-community/eza/releases/download/{tag}/eza_{asset_arch}-unknown-linux-gnu.tar.gz"
        if name == "lazygit":
            tag = target if target != "latest" else github_latest_tag("jesseduffield/lazygit")
            clean = tag.lstrip("v")
            asset_arch = arch
            return f"https://github.com/jesseduffield/lazygit/releases/download/{tag}/lazygit_{clean}_Linux_{asset_arch}.tar.gz"

    return source_url


def checksum_for_source(source_url: str) -> str:
    parsed = urllib.parse.urlparse(source_url)
    if parsed.scheme == "file":
        path = pathlib.Path(urllib.request.url2pathname(parsed.path))
        data = path.read_bytes()
        return hashlib.sha256(data).hexdigest()

    source_path = pathlib.Path(source_url)
    if source_path.exists():
        return hashlib.sha256(source_path.read_bytes()).hexdigest()

    request = urllib.request.Request(source_url, headers={"User-Agent": "codex-dotfiles-lock"})
    digest = hashlib.sha256()
    with urllib.request.urlopen(request) as response:
        while True:
            chunk = response.read(1024 * 1024)
            if not chunk:
                break
            digest.update(chunk)
    return digest.hexdigest()


def extract_existing_records(existing_deps: dict[str, Any]) -> dict[str, dict[tuple[str, str], dict[str, Any]]]:
    normalized: dict[str, dict[tuple[str, str], dict[str, Any]]] = {}
    leaf_keys = {"platform", "arch", "installer", "version", "source_url", "checksum", "filename"}

    for name, dep in existing_deps.items():
        records: dict[tuple[str, str], dict[str, Any]] = {}
        if not isinstance(dep, dict):
            continue

        for platform_name, platform_value in dep.items():
            if platform_name == "target" or not isinstance(platform_value, dict):
                continue

            if platform_value and all(isinstance(value, dict) for value in platform_value.values()) and not (leaf_keys & set(platform_value.keys())):
                for arch_name, entry in platform_value.items():
                    if isinstance(entry, dict):
                        records[(platform_name, arch_name)] = dict(entry)
            else:
                arch_name = str(platform_value.get("arch", ""))
                if arch_name:
                    records[(platform_name, arch_name)] = dict(platform_value)

        if records:
            normalized[name] = records

    return normalized


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("manifest")
    parser.add_argument("lock")
    parser.add_argument("--platform", required=True)
    parser.add_argument("--manifest-commit", required=True)
    args = parser.parse_args()

    manifest_path = pathlib.Path(args.manifest)
    lock_path = pathlib.Path(args.lock)
    manifest = load_toml(manifest_path)
    existing = load_toml(lock_path)

    deps = manifest.get("deps", {})
    existing_deps = existing.get("deps", {})
    existing_records = extract_existing_records(existing_deps)
    platform_name = args.platform
    arch_name = current_arch()
    generated_at = utc_now()

    output: list[str] = []
    output.append("[meta]")
    output.append(f"generated_at = {render_value(generated_at)}")
    output.append('generator_version = "1"')
    output.append(f"manifest_commit = {render_value(args.manifest_commit)}")
    output.append("")

    for name, dep in deps.items():
        command = dep["command"]
        target = dep["target"]
        current_entry = dep.get(platform_name, {})
        if not current_entry:
            continue
        installer = current_entry.get("installer", "")
        if not should_write_lock(installer):
            continue
        version = run_version(command, installer, name)

        write_table(output, f"deps.{name}", {"target": target})

        platform_rows: dict[tuple[str, str], dict[str, Any]] = {}
        for (existing_platform, existing_arch), existing_row in existing_records.get(name, {}).items():
            if existing_platform == platform_name and existing_arch == arch_name:
                continue
            platform_rows[(existing_platform, existing_arch)] = dict(existing_row)

        artifact_url = resolve_artifact_url(name, platform_name, target, version, installer, current_entry.get("source", ""))
        checksum = ""
        if installer in {"official_tarball", "official_release"}:
            checksum = checksum_for_source(artifact_url)

        platform_rows[(platform_name, arch_name)] = {
            "platform": platform_name,
            "arch": arch_name,
            "installer": installer,
            "version": version,
            "source_url": artifact_url,
            "checksum": checksum,
            "filename": derive_filename(name, platform_name, version, installer),
        }

        for (existing_platform, existing_arch), row in sorted(platform_rows.items()):
            write_table(output, f"deps.{name}.{existing_platform}.{existing_arch}", row)

    lock_path.write_text("\n".join(output).rstrip() + "\n", encoding="utf-8")


if __name__ == "__main__":
    main()
