"""Update checker for cokodo-agent.

Checks PyPI for newer versions in a background thread and displays
a pip-style notice at the end of the command.

The check is cached for 24 hours so it does not add latency to
subsequent invocations.
"""

from __future__ import annotations

import json
import threading
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Optional, Tuple

from cokodo_agent.config import DEFAULT_CACHE_DIR, VERSION

PYPI_URL = "https://pypi.org/pypi/cokodo-agent/json"
_CHECK_INTERVAL = 86400  # 24 hours
_CACHE_FILE = DEFAULT_CACHE_DIR / "update_check.json"
_NETWORK_TIMEOUT = 3  # seconds


def _parse_version(v: str) -> tuple:
    """Parse a version string into a comparable tuple.

    Strips pre-release suffixes (a/b/rc) so e.g. '1.6.0a1' < '1.6.0'.
    """
    try:
        parts = []
        for seg in v.strip().split(".")[:3]:
            # Strip any non-numeric suffix (alpha, beta, rc)
            num = ""
            for ch in seg:
                if ch.isdigit():
                    num += ch
                else:
                    break
            parts.append(int(num) if num else 0)
        return tuple(parts)
    except Exception:
        return (0,)


def _fetch_latest_version() -> Optional[str]:
    """Fetch the latest cokodo-agent version from PyPI.

    Returns the version string or None if the request fails.
    Uses only stdlib so no extra dependency is needed.
    """
    try:
        req = urllib.request.Request(
            PYPI_URL,
            headers={"User-Agent": f"cokodo-agent/{VERSION} update-checker"},
        )
        with urllib.request.urlopen(req, timeout=_NETWORK_TIMEOUT) as resp:
            data = json.loads(resp.read().decode("utf-8"))
            return data["info"]["version"]
    except Exception:
        return None


def _load_cache() -> dict:
    try:
        if _CACHE_FILE.exists():
            return json.loads(_CACHE_FILE.read_text(encoding="utf-8"))
    except Exception:
        pass
    return {}


def _save_cache(data: dict) -> None:
    try:
        _CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
        _CACHE_FILE.write_text(
            json.dumps(data, ensure_ascii=False), encoding="utf-8"
        )
    except Exception:
        pass


class UpdateChecker:
    """Non-blocking update checker.

    Usage::

        checker = UpdateChecker()
        checker.start()          # starts background thread if cache is stale
        ...
        result = checker.result()  # (current, latest) or None
    """

    def __init__(self) -> None:
        self._result: Optional[Tuple[str, str]] = None
        self._thread: Optional[threading.Thread] = None
        self._done = threading.Event()

    def start(self) -> None:
        """Start the update check.

        If the cache is fresh the result is set immediately (no network call).
        Otherwise a daemon thread is spawned to fetch from PyPI.
        """
        cache = _load_cache()
        now = time.time()

        if (now - cache.get("ts", 0)) < _CHECK_INTERVAL:
            latest = cache.get("latest")
            if latest and _parse_version(latest) > _parse_version(VERSION):
                self._result = (VERSION, latest)
            self._done.set()
            return

        self._thread = threading.Thread(target=self._run, daemon=True)
        self._thread.start()

    def _run(self) -> None:
        try:
            latest = _fetch_latest_version()
            now = time.time()
            if latest:
                _save_cache({"ts": now, "latest": latest})
                if _parse_version(latest) > _parse_version(VERSION):
                    self._result = (VERSION, latest)
            else:
                cache = _load_cache()
                cache["ts"] = now
                _save_cache(cache)
        finally:
            self._done.set()

    def result(self, timeout: float = 2.0) -> Optional[Tuple[str, str]]:
        """Return ``(current, latest)`` if an update is available, else ``None``.

        Waits up to *timeout* seconds for the background thread to finish.
        """
        self._done.wait(timeout=timeout)
        return self._result


def format_update_notice(current: str, latest: str) -> str:
    """Return a plain-text update notice (used in tests / non-rich output)."""
    return (
        f"A new release of cokodo-agent is available: {current} → {latest}\n"
        f"Run: pip install --upgrade cokodo-agent"
    )


def print_update_notice(current: str, latest: str) -> None:
    """Print a pip-style update notice to stderr using Rich."""
    try:
        from rich.console import Console
        from rich.panel import Panel

        err = Console(stderr=True)
        err.print()
        err.print(
            Panel(
                f"A new release of [bold cyan]cokodo-agent[/bold cyan] is available: "
                f"[yellow]{current}[/yellow] → [bold green]{latest}[/bold green]\n"
                f"Run: [bold cyan]pip install --upgrade cokodo-agent[/bold cyan]",
                title="[yellow]Update available[/yellow]",
                border_style="yellow",
                expand=False,
            )
        )
    except Exception:
        # Fallback in case Rich is somehow unavailable
        import sys

        print(f"\n{format_update_notice(current, latest)}\n", file=sys.stderr)
