from __future__ import annotations

from dataclasses import dataclass
from typing import Any


@dataclass(frozen=True)
class UpdateManifest:
    version: str
    channel: str
    signature: str
    url: str
    signed: bool = False
    signature_available: bool = False
    signature_valid: bool = False
    signature_algorithm: str = ""
    public_key_id: str = ""


@dataclass(frozen=True)
class ReleaseDecision:
    ok: bool
    update_available: bool
    reason: str
    target_version: str = ""


def release_payload_checksum(payload: dict[str, Any]) -> str:
    """Return normalized SHA256 from release metadata (API or latest.json)."""
    return str(payload.get("checksum") or payload.get("checksum_sha256") or "").strip().lower()


def resolve_upgrade_current_version(
    *,
    config_version: str,
    package_version: str,
    runtime_version: str,
) -> str:
    """Pick the installed version for upgrade comparison.

    Prefer the Debian package version when present so UI and update checks agree
    with dpkg. Fall back to the running runtime constant, then config.
    """
    pkg = str(package_version or "").strip()
    runtime = str(runtime_version or "").strip()
    config = str(config_version or "").strip()
    if pkg:
        return pkg
    if runtime:
        return runtime
    return config


def versions_comparable_gte(left: str, right: str) -> bool:
    """Return True when *left* is greater than or equal to *right*."""
    return _parse_version(left) >= _parse_version(right)


def package_version_covers_target(package_version: str, target_version: str) -> bool:
    """Return True when the installed Debian package is already at *target_version* or newer."""
    pkg = str(package_version or "").strip()
    target = str(target_version or "").strip()
    if not pkg or not target:
        return False
    return versions_comparable_gte(pkg, target)


def _parse_version(value: str) -> tuple[int, int, int, int]:
    parts: list[int] = []
    for token in str(value or "").split("."):
        digits = ""
        for ch in token:
            if ch.isdigit():
                digits += ch
            else:
                break
        parts.append(int(digits or "0"))
    while len(parts) < 4:
        parts.append(0)
    return int(parts[0]), int(parts[1]), int(parts[2]), int(parts[3])


def evaluate_release_for_upgrade(
    *,
    release_payload: dict[str, Any],
    current_version: str,
    update_channel: str,
) -> ReleaseDecision:
    state = str(release_payload.get("state") or "").strip().upper()
    if state not in {"AVAILABLE", "INTERNAL_ONLY"}:
        return ReleaseDecision(False, False, f"release_state_{state.lower() or 'unknown'}")

    target_version = str(release_payload.get("version") or "").strip()
    if not target_version:
        return ReleaseDecision(False, False, "missing_release_version")

    if _parse_version(target_version) <= _parse_version(current_version):
        return ReleaseDecision(True, False, "already_up_to_date", target_version=target_version)

    channel = str(release_payload.get("channel") or "").strip().lower()
    configured_channel = str(update_channel or "").strip().lower()
    if configured_channel == "disabled":
        return ReleaseDecision(False, False, "channel_disabled", target_version=target_version)

    if configured_channel and configured_channel not in {"stable", channel}:
        return ReleaseDecision(False, False, "release_channel_mismatch", target_version=target_version)

    filename = str(release_payload.get("filename") or "").strip().lower()
    if not filename.endswith(".deb"):
        return ReleaseDecision(False, False, "unsupported_package_format", target_version=target_version)

    checksum = release_payload_checksum(release_payload)
    if len(checksum) != 64:
        return ReleaseDecision(False, False, "invalid_release_checksum", target_version=target_version)

    return ReleaseDecision(True, True, "update_available", target_version=target_version)


def verify_update_signature(manifest: UpdateManifest, public_key: str) -> bool:
    """Metadata-level signature policy gate for desktop package upgrades."""
    if str(manifest.signature_algorithm or "").strip().lower() != "ed25519":
        return False
    if not manifest.signed:
        return False
    if not manifest.signature_available:
        return False
    if not manifest.signature_valid:
        return False
    key = str(public_key or manifest.public_key_id or "").strip().lower()
    return len(key) == 64


def release_install_allowed(
    manifest: UpdateManifest,
    *,
    trusted_official_channel: bool = False,
) -> tuple[bool, str]:
    """Return whether a downloaded release may be installed on this device."""
    if verify_update_signature(manifest, manifest.public_key_id):
        return True, "signed_release"
    if trusted_official_channel and not manifest.signed:
        return True, "trusted_official_checksum_channel"
    return False, "release_signature_policy_failed"
