"""Utilities for checking and applying worai CLI updates."""

from __future__ import annotations

from dataclasses import dataclass
import json
from pathlib import Path
import re
import shlex
import subprocess
import sys
import time
from typing import Callable
from urllib import error, request

PYPI_JSON_URL = "https://pypi.org/pypi/worai/json"
CACHE_TTL_SECONDS = 60 * 60 * 24


@dataclass
class UpdateCheckResult:
    current_version: str
    latest_version: str | None
    update_available: bool
    installer: str
    upgrade_command: list[str]
    used_cache: bool = False


def command_to_string(command: list[str]) -> str:
    return " ".join(shlex.quote(part) for part in command)


def detect_installer(
    env: dict[str, str] | None = None,
    python_executable: str | None = None,
) -> str:
    if env is None:
        env = {}
    executable = (python_executable or sys.executable).lower()
    if (
        "/pipx/venvs/" in executable
        or "pipx\\venvs\\" in executable
        or bool(env.get("PIPX_HOME"))
        or bool(env.get("PIPX_BIN_DIR"))
    ):
        return "pipx"
    return "pip"


def build_upgrade_command(
    installer: str,
    python_executable: str | None = None,
) -> list[str]:
    if installer == "pipx":
        return ["pipx", "upgrade", "worai"]
    return [python_executable or sys.executable, "-m", "pip", "install", "--upgrade", "worai"]


def get_cache_path(env: dict[str, str] | None = None) -> Path:
    if env is None:
        env = {}
    cache_home = env.get("XDG_CACHE_HOME")
    if cache_home:
        base = Path(cache_home).expanduser()
    else:
        base = Path("~/.cache").expanduser()
    return base / "worai" / "update-check.json"


def _parse_version(value: str) -> tuple[int, ...] | None:
    if not value:
        return None
    parts: list[int] = []
    for part in value.split("."):
        match = re.match(r"^(\d+)", part)
        if match is None:
            return None
        parts.append(int(match.group(1)))
    if not parts:
        return None
    return tuple(parts)


def is_newer_version(latest: str, current: str) -> bool:
    latest_tuple = _parse_version(latest)
    current_tuple = _parse_version(current)
    if latest_tuple is None or current_tuple is None:
        return False
    size = max(len(latest_tuple), len(current_tuple))
    latest_norm = latest_tuple + (0,) * (size - len(latest_tuple))
    current_norm = current_tuple + (0,) * (size - len(current_tuple))
    return latest_norm > current_norm


def _read_cached_latest(cache_path: Path, now_ts: float) -> str | None:
    try:
        payload = json.loads(cache_path.read_text(encoding="utf-8"))
    except (FileNotFoundError, OSError, json.JSONDecodeError):
        return None

    checked_at = payload.get("checked_at")
    latest_version = payload.get("latest_version")
    if not isinstance(checked_at, (int, float)) or not isinstance(latest_version, str):
        return None
    if now_ts - float(checked_at) > CACHE_TTL_SECONDS:
        return None
    return latest_version


def _write_cache(cache_path: Path, now_ts: float, latest_version: str) -> None:
    payload = {
        "checked_at": now_ts,
        "latest_version": latest_version,
    }
    try:
        cache_path.parent.mkdir(parents=True, exist_ok=True)
        cache_path.write_text(json.dumps(payload), encoding="utf-8")
    except OSError:
        return


def fetch_latest_version(timeout_seconds: float = 1.5) -> str | None:
    headers = {"Accept": "application/json", "User-Agent": "worai-update-check"}
    req = request.Request(PYPI_JSON_URL, headers=headers)
    try:
        with request.urlopen(req, timeout=timeout_seconds) as response:
            payload = json.load(response)
    except (error.URLError, TimeoutError, OSError, json.JSONDecodeError):
        return None

    latest = payload.get("info", {}).get("version")
    if isinstance(latest, str) and latest:
        return latest
    return None


def check_for_update(
    current_version: str,
    env: dict[str, str] | None = None,
    force_refresh: bool = False,
    timeout_seconds: float = 1.5,
    cache_path: Path | None = None,
    now_ts: float | None = None,
    fetcher: Callable[[float], str | None] | None = None,
) -> UpdateCheckResult:
    if env is None:
        env = {}
    if cache_path is None:
        cache_path = get_cache_path(env)
    if now_ts is None:
        now_ts = time.time()
    if fetcher is None:
        fetcher = fetch_latest_version

    installer = detect_installer(env=env)
    upgrade_command = build_upgrade_command(installer)

    latest_version: str | None = None
    used_cache = False
    if not force_refresh:
        latest_version = _read_cached_latest(cache_path, now_ts)
        used_cache = latest_version is not None

    if latest_version is None:
        latest_version = fetcher(timeout_seconds)
        if latest_version:
            _write_cache(cache_path, now_ts, latest_version)

    update_available = bool(latest_version and is_newer_version(latest_version, current_version))
    return UpdateCheckResult(
        current_version=current_version,
        latest_version=latest_version,
        update_available=update_available,
        installer=installer,
        upgrade_command=upgrade_command,
        used_cache=used_cache,
    )


def run_upgrade_command(command: list[str]) -> int:
    try:
        completed = subprocess.run(command, check=False)
    except FileNotFoundError:
        return 127
    return int(completed.returncode)
