"""Safe release-manifest client for the Blender Add-on.

Blender loads Add-on Python modules into its own process, so a release check
only informs the user and opens the official package URL. It never replaces
the installed Add-on while Blender is running.
"""

from dataclasses import dataclass, replace
import threading
import time
from typing import Optional
from urllib import parse as urlparse

from .auth_client import (
    AuthorizationError,
    _endpoint,
    _request_json,
    _require_online_access,
    get_service_settings,
)
from .config import ADDON_VERSION


class UpdateCheckError(AuthorizationError):
    pass


@dataclass(frozen=True)
class AddonRelease:
    latest_version: str
    release_url: Optional[str]
    release_notes: Optional[str]
    update_available: bool


@dataclass(frozen=True)
class UpdateState:
    phase: str = "not_checked"
    message: str = "Check 3DRipper for Add-on updates."
    release: Optional[AddonRelease] = None
    checked_at: float = 0.0


_state_lock = threading.RLock()
_state = UpdateState()


def _set_state(**changes):
    global _state
    with _state_lock:
        _state = replace(_state, **changes)
        return _state


def get_update_state():
    with _state_lock:
        return _state


def _version_key(value):
    core = str(value or "").strip().split("+", 1)[0].split("-", 1)[0]
    parts = core.split(".")
    if len(parts) != 3 or any(not part.isdigit() for part in parts):
        raise UpdateCheckError("3DRipper returned an invalid Add-on version.")
    return tuple(int(part) for part in parts)


def _validate_release_url(value):
    if value is None:
        return None
    if not isinstance(value, str) or not value.strip():
        raise UpdateCheckError("3DRipper returned an invalid Add-on release URL.")
    url = value.strip()
    parsed = urlparse.urlsplit(url)
    hostname = (parsed.hostname or "").lower()
    if parsed.scheme not in {"http", "https"} or not parsed.netloc:
        raise UpdateCheckError("3DRipper returned an invalid Add-on release URL.")
    if parsed.scheme != "https" and hostname not in {"localhost", "127.0.0.1", "::1"}:
        raise UpdateCheckError("Official Add-on releases must use HTTPS.")
    return url


def check_for_update(context=None):
    """Fetch the public 3DRipper release manifest without sending account data."""
    _require_online_access()
    service_url, timeout = get_service_settings(context)
    _set_state(phase="checking", message="Checking 3DRipper for Add-on updates.", release=None)

    try:
        payload = _request_json(
            _endpoint(service_url, "/api/blender/v1/release"),
            timeout=timeout,
        )
        if not isinstance(payload, dict) or payload.get("success") is not True:
            raise UpdateCheckError("3DRipper returned an invalid Add-on release response.")
        data = payload.get("data")
        if not isinstance(data, dict):
            raise UpdateCheckError("3DRipper returned an invalid Add-on release response.")
        latest_version = str(data.get("latestVersion") or "")
        latest_key = _version_key(latest_version)
        update_available = latest_key > _version_key(ADDON_VERSION)
        release = AddonRelease(
            latest_version=latest_version,
            release_url=_validate_release_url(data.get("releaseUrl")),
            release_notes=str(data["releaseNotes"]) if data.get("releaseNotes") else None,
            update_available=update_available,
        )
    except AuthorizationError as error:
        _set_state(phase="error", message=str(error), release=None, checked_at=time.monotonic())
        raise

    if release.update_available:
        message = "Version {} is available.".format(release.latest_version)
        phase = "available"
    else:
        message = "Add-on {} is up to date.".format(ADDON_VERSION)
        phase = "up_to_date"
    _set_state(phase=phase, message=message, release=release, checked_at=time.monotonic())
    return release
