"""KiCad version check + upgrade via the OFFICIAL downloads.kicad.org installer.

Why not winget: winget's KiCad package lags the real KiCad release (often by a
minor version or more), and it can't pin a specific build. The official
`downloads.kicad.org` NSIS installer is the canonical, signed artifact — so the
bridge fetches THAT and installs it silently (`/S`).

- `check_for_updates` — read-only: current installed version vs latest STABLE
  (resolved from KiCad's GitLab tags, baked fallback on a network blip), plus the
  "your KiCad predates the IPC/kipy API — upgrade" nudge.
- `upgrade` — downloads the official installer and runs it silently. Needs
  elevation for Program Files, so the user may see (and approve) a UAC prompt.
"""

import json
import re
import sys
import tempfile
import urllib.error
import urllib.request
from pathlib import Path

import kicad_detect
import proc

# Baked fallback if the GitLab tag query fails (network blip). Bump on new stable.
_FALLBACK_LATEST = "10.0.4"
_TAGS_URL = (
    "https://gitlab.com/api/v4/projects/kicad%2Fcode%2Fkicad/repository/tags?per_page=30"
)
_DL_URL = "https://downloads.kicad.org/kicad/windows/explore/stable/download/kicad-{v}-x86_64.exe"
_UA = "adom-desktop-kicad-bridge"


def _is_stable(tag: str) -> bool:
    """True for a real stable tag like 10.0.4 / 9.0.9.1. Drops `-rcN` prereleases
    and KiCad's `X.99.*` nightly/development tags (minor component == 99)."""
    if not re.fullmatch(r"\d+(?:\.\d+)+", tag):
        return False
    parts = tag.split(".")
    return not (len(parts) >= 2 and parts[1] == "99")


def latest_stable_version() -> tuple[str, str]:
    """Return (version, source) where source is 'gitlab' or 'fallback'."""
    try:
        req = urllib.request.Request(_TAGS_URL, headers={"User-Agent": _UA})
        with urllib.request.urlopen(req, timeout=10) as r:
            tags = [t["name"] for t in json.loads(r.read().decode())]
        stables = sorted(
            (t for t in tags if _is_stable(t)),
            key=kicad_detect._version_tuple,
            reverse=True,
        )
        if stables:
            return stables[0], "gitlab"
    except Exception:
        pass
    return _FALLBACK_LATEST, "fallback"


def download_url(version: str) -> str:
    return _DL_URL.format(v=version)


def handle_check_for_updates(kicad_info: dict, args: dict) -> dict:
    """Read-only: is a newer KiCad available, and does the current one have the IPC API?"""
    current = kicad_info.get("version") if kicad_info else None
    latest, src = latest_stable_version()
    cur_t = kicad_detect._version_tuple(current) if current else (0,)
    lat_t = kicad_detect._version_tuple(latest)
    upgrade_available = current is None or lat_t > cur_t
    ipc = bool(kicad_info.get("ipcApiAvailable")) if kicad_info else False

    if current is None:
        rec = (
            f"KiCad isn't installed. Run kicad_upgrade to install {latest} "
            f"from the official downloads.kicad.org installer."
        )
    elif upgrade_available and not ipc:
        rec = (
            f"Your KiCad {current} predates the modern IPC / kipy automation API — "
            f"upgrade to {latest} to unlock it (plus the latest fixes). Run kicad_upgrade."
        )
    elif upgrade_available:
        rec = f"KiCad {latest} is available (you're on {current}). Run kicad_upgrade to update."
    else:
        rec = f"KiCad {current} is up to date (latest stable is {latest})."

    return {
        "success": True,
        "currentVersion": current,
        "latestVersion": latest,
        "latestSource": src,
        "upgradeAvailable": upgrade_available,
        "ipcApiAvailable": ipc,
        "downloadUrl": download_url(latest),
        "recommendation": rec,
        "_hint": (
            "Run kicad_upgrade '{}' to install the latest (downloads the OFFICIAL "
            "downloads.kicad.org installer + silent /S install — NOT winget, which lags). "
            'Pass {"version":"X.Y.Z"} to pin a specific build.'
        ),
    }


def handle_upgrade(kicad_info: dict, args: dict) -> dict:
    """Download the official KiCad installer and run it silently (/S)."""
    if sys.platform != "win32":
        return {
            "success": False,
            "error": "kicad_upgrade is Windows-only (it downloads the .exe installer).",
            "errorCode": "unsupported_platform",
        }

    target = args.get("version") or latest_stable_version()[0]
    current = kicad_info.get("version") if kicad_info else None
    if (
        current
        and kicad_detect._version_tuple(current) >= kicad_detect._version_tuple(target)
        and not args.get("force")
    ):
        return {
            "success": True,
            "alreadyCurrent": True,
            "currentVersion": current,
            "targetVersion": target,
            "_hint": f'KiCad {current} >= {target}; nothing to do. Pass {{"force":true}} to reinstall.',
        }

    url = download_url(target)
    dest = Path(tempfile.gettempdir()) / f"kicad-{target}-x86_64.exe"

    # Download — urllib follows the downloads.kicad.org -> s3.cern.ch redirect.
    try:
        req = urllib.request.Request(url, headers={"User-Agent": _UA})
        total = 0
        with urllib.request.urlopen(req, timeout=120) as r, open(dest, "wb") as f:
            while True:
                chunk = r.read(1 << 20)
                if not chunk:
                    break
                f.write(chunk)
                total += len(chunk)
    except (urllib.error.URLError, OSError) as e:
        return {
            "success": False,
            "error": f"download failed: {e}",
            "errorCode": "download_failed",
            "url": url,
        }

    # Sanity: a real KiCad installer is a >50 MB Windows PE (MZ header).
    if total < 50 * 1024 * 1024:
        return {
            "success": False,
            "error": f"download too small ({total} bytes) — not a full installer",
            "errorCode": "download_incomplete",
            "url": url,
            "path": str(dest),
        }
    with open(dest, "rb") as f:
        if f.read(2) != b"MZ":
            return {
                "success": False,
                "error": "downloaded file is not a Windows executable",
                "errorCode": "download_corrupt",
                "path": str(dest),
            }

    # Silent install (NSIS /S). Installing into Program Files needs elevation, so
    # the user may get a UAC prompt to approve. Block until the installer exits.
    try:
        result = proc.run([str(dest), "/S"], timeout=900)
        rc = result.returncode
    except Exception as e:
        return {
            "success": False,
            "error": f"installer launch failed: {e}",
            "errorCode": "install_failed",
            "path": str(dest),
            "_hint": "If a UAC prompt appeared, approve it; or run the downloaded installer manually.",
        }

    # Confirm by re-scanning installed versions.
    after = kicad_detect.detect_all_kicad_versions()
    installed = [v["version"] for v in after]
    now_has = any(
        kicad_detect._version_tuple(v) >= kicad_detect._version_tuple(target)
        for v in installed
    )
    return {
        "success": bool(now_has),
        "upgraded": bool(now_has),
        "targetVersion": target,
        "previousVersion": current,
        "installerExit": rc,
        "installedVersions": installed,
        "downloadUrl": url,
        "_hint": (
            f"KiCad {target} installed."
            if now_has
            else (
                f"Installer ran (exit {rc}) but {target} wasn't detected — it may have needed a "
                f"UAC approval, or the bridge needs a restart to re-scan. Installed: "
                f"{', '.join(installed) or 'none'}."
            )
        ),
    }
