import json
import re
import subprocess
import urllib.error
import urllib.request

from aicage._logging import get_logger
from aicage._network import classify_network_failure, host_from_url
from aicage.constants import PYPI_VERSION_CHECK_TIMEOUT_SECONDS
from aicage.runtime.prompts.confirm import prompt_update_aicage

_PYPI_URL: str = "https://pypi.org/pypi/aicage/json"
_UPGRADE_COMMAND: str = "pipx upgrade aicage"
_UNKNOWN_VERSION: str = "0.0.0"


def _check_for_update(current_version: str) -> str | None:
    logger = get_logger()
    host = host_from_url(_PYPI_URL)
    try:
        request = urllib.request.Request(_PYPI_URL, headers={"Accept": "application/json"})
        with urllib.request.urlopen(request, timeout=PYPI_VERSION_CHECK_TIMEOUT_SECONDS) as response:
            payload = json.loads(response.read().decode("utf-8"))
    except (urllib.error.URLError, TimeoutError) as exc:
        logger.warning(
            "Version check failed (operation=pypi_version_check, host=%s, category=%s): %s",
            host,
            classify_network_failure(exc),
            exc,
        )
        return None
    except json.JSONDecodeError as exc:
        logger.warning("Version check failed (operation=pypi_version_check, host=%s): %s", host, exc)
        return None

    latest_version = str(payload.get("info", {}).get("version", "")).strip()
    if not latest_version:
        return None

    if _is_newer(latest_version, current_version):
        return latest_version

    return None


def maybe_prompt_update(current_version: str) -> bool:
    if current_version == _UNKNOWN_VERSION:
        return False
    latest_version = _check_for_update(current_version)
    if not latest_version:
        return False

    if prompt_update_aicage(current_version, latest_version):
        return _run_upgrade()
    print(f"Update with: {_UPGRADE_COMMAND}")
    return False


def _run_upgrade() -> bool:
    logger = get_logger()
    try:
        result = subprocess.run(
            _UPGRADE_COMMAND.split(),
            check=False,
            capture_output=True,
            text=True,
        )
    except FileNotFoundError as exc:
        logger.error("Upgrade command failed: %s", exc)
        print(f"Unable to run '{_UPGRADE_COMMAND}'. Please run it manually.")
        return False

    if result.returncode != 0:
        logger.error(
            "Upgrade command failed (%s): %s",
            result.returncode,
            result.stderr.strip(),
        )
        print(f"Upgrade failed. Please run '{_UPGRADE_COMMAND}' manually.")
        return False
    return True


def _is_newer(latest_version: str, current_version: str) -> bool:
    latest = _parse_version(latest_version)
    current = _parse_version(current_version)
    if latest is None or current is None:
        return False
    length = max(len(latest), len(current))
    latest_padded = latest + (0,) * (length - len(latest))
    current_padded = current + (0,) * (length - len(current))
    return latest_padded > current_padded


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