"""Helpers for checking if a new CLI version is available."""

import json
from dataclasses import dataclass
from datetime import datetime, timedelta
from pathlib import Path

import requests
from packaging.version import Version
from pytz import UTC
from rich.panel import Panel

from slices_cli_ai import err_console
from slices_cli_ai.const import CONFIG_DIR


@dataclass
class AvailableVersionInfo:
    """Info about the available CLI version in the package repository."""

    stable_version: Version
    pre_version: Version | None
    info_updated_at: datetime


@dataclass
class InstalledVersionInfo:
    """Info about the locally installed CLI version."""

    version: Version

    @property
    def is_prerelease(self) -> bool:
        """Return True if a pre-release installed locally."""
        return self.version.is_prerelease


SLICES_CLI_PACKAGE_INFO_URL = "https://doc.slices-ri.eu/pypi/slices-cli/json"


def download_available_versions() -> AvailableVersionInfo:
    """Contact the package repository, to get info about the available CLI version."""
    response = requests.get(SLICES_CLI_PACKAGE_INFO_URL, timeout=1)
    response.raise_for_status()
    version_info = response.json()
    newest_stable_version = None
    newest_pre_version = None
    for version_str in version_info["releases"]:
        version = Version(version_str)
        if version.is_prerelease:
            if newest_pre_version is None or version > newest_pre_version:
                newest_pre_version = version
        elif newest_stable_version is None or version > newest_stable_version:
            newest_stable_version = version
    assert newest_stable_version is not None
    return AvailableVersionInfo(
        stable_version=newest_stable_version, pre_version=newest_pre_version, info_updated_at=datetime.now(UTC)
    )


def get_running_version() -> InstalledVersionInfo:
    """Return the running Slices CLI ai version info."""
    from importlib.metadata import version as metadata_version  # noqa: PLC0415

    return InstalledVersionInfo(Version(metadata_version("slices-cli")))


config_dir_location = Path.home() / CONFIG_DIR
version_cache_file_location = config_dir_location / "versions_cache.json"
version_warning_date_file_location = config_dir_location / "versions_warning.json"


def save_version_cache(version_info: AvailableVersionInfo):
    """Save the AvailableVersionInfo to the cache."""
    try:
        with version_cache_file_location.open("w") as version_cache_file:
            json.dump(
                {
                    "stable_version": str(version_info.stable_version),
                    "pre_version": str(version_info.pre_version),
                    "info_updated_at": version_info.info_updated_at.timestamp(),
                },
                version_cache_file,
            )
    except:  # noqa: E722, S110
        # ignore
        # raise
        pass


def load_version_cache() -> AvailableVersionInfo | None:
    """Load the AvailableVersionInfo from the cache."""
    if not version_cache_file_location.exists():
        return None
    try:
        with version_cache_file_location.open() as version_cache_file:
            version_cache = json.load(version_cache_file)
            return AvailableVersionInfo(
                stable_version=Version(version_cache["stable_version"]),
                pre_version=Version(version_cache["pre_version"]),
                info_updated_at=datetime.fromtimestamp(version_cache["info_updated_at"], tz=UTC),
            )
    except:  # noqa: E722
        # ignore
        # raise
        return None


def save_version_warning_date(warned_at: datetime):
    """Save the version warning date."""
    try:
        with version_warning_date_file_location.open("w") as version_warning_date_file:
            json.dump({"warned_at": warned_at.timestamp()}, version_warning_date_file)
    except:  # noqa: E722, S110
        # ignore
        pass


def load_version_warning_date() -> datetime | None:
    """Load the version warning date."""
    if not version_warning_date_file_location.exists():
        return None
    try:
        with version_warning_date_file_location.open() as version_warning_date_file:
            version_warning_date = json.load(version_warning_date_file)
            return datetime.fromtimestamp(version_warning_date["warned_at"], tz=UTC)
    except:  # noqa: E722
        # ignore
        return None


UPGRADE_CHECK_INTERVAL = timedelta(hours=1)
UPGRADE_WARN_INTERVAL = timedelta(hours=20)


def is_newer_version_available() -> tuple[InstalledVersionInfo, AvailableVersionInfo] | None:
    """Return version info, or None if no newer version available."""

    available = load_version_cache()
    if available is None or available.info_updated_at < datetime.now(UTC) - UPGRADE_CHECK_INTERVAL:
        available = download_available_versions()
        if available is None:
            return None
        save_version_cache(available)
    installed = get_running_version()
    if installed.is_prerelease:
        if available.pre_version and installed.version < available.pre_version:
            return installed, available
    elif installed.version < available.stable_version:
        return installed, available
    return None


def handle_newer_version_available():
    """Check if new version is available, and output info to console if it is."""
    try:
        has_newer = is_newer_version_available()
        if not has_newer:
            return
        warned_at = load_version_warning_date()
        if warned_at is not None and warned_at > datetime.now(UTC) - UPGRADE_WARN_INTERVAL:
            # Already warned recently
            return
        installed, available = has_newer
        new_v = available.pre_version if installed.is_prerelease else available.stable_version
        if installed.is_prerelease:
            command = "pip install --upgrade --pre slices-cli --extra-index-url=https://doc.slices-ri.eu/pypi/"
        else:
            command = "pip install --upgrade slices-cli --extra-index-url=https://doc.slices-ri.eu/pypi/"
        err_console.print(
            Panel(
                f"""Current version: {installed.version}
    New version: [bold]{new_v}[/bold]
   Upgrade with: [italic]{command}[/italic]""",
                title="[yellow]New Slices CLI ai version available",
                expand=False,
                style="yellow",
                padding=(1, 3),
            )
        )
        save_version_warning_date(datetime.now(UTC))
    except Exception as e:
        # If anything at all goes wrong, ignore and return no new version is available
        err_console.print(f"Problem checking for new version: {e}")
        return
