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

import json
from dataclasses import dataclass
from datetime import datetime, timedelta
from importlib.metadata import version as metadata_version

import requests
from packaging.version import Version
from pytz import UTC
from slices_cli_core import err_console

from . import cache_dir_location, console_trace


@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=5)
    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 version info."""
    return InstalledVersionInfo(Version(metadata_version("slices-cli")))


version_cache_file_location = cache_dir_location / "cache_package_info.json"
version_warning_date_file_location = cache_dir_location / "cache_new_version_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 do_upgrade_check():
    """Check if a new version is available, and output info to console if it is."""
    from rich.panel import Panel  # noqa: PLC0415  lazy loading of rich

    try:
        console_trace.print("[bright_black]Checking for new version...")
        has_newer = is_newer_version_available()
        if not has_newer:
            console_trace.print("[bright_black]   No new version available.")
            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
            console_trace.print("[bright_black]   New version available, but 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 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
