"""
Update manifest helpers for EMDS QGIS plugin releases.
"""

from __future__ import annotations

import json
import platform
import re
import urllib.error
import urllib.request
from configparser import ConfigParser
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Optional


DEFAULT_MANIFEST_URL = "https://emds.mountain-viewgroup.com/downloads/qgis/latest.json"
DEFAULT_DOWNLOAD_PAGE_URL = "https://emds.mountain-viewgroup.com/downloads/qgis/"
USER_AGENT = "EMDS-QGIS-Plugin-Updater/1.0"


@dataclass(frozen=True)
class UpdateCheckResult:
    status: str
    current_version: str
    latest_version: Optional[str]
    platform_id: str
    message: str
    zip_url: Optional[str] = None
    repository_url: Optional[str] = None
    download_page_url: Optional[str] = None
    changelog: Optional[str] = None

    @property
    def update_available(self) -> bool:
        return self.status == "update_available"


def detect_platform_id() -> str:
    """Return the EMDS release platform identifier for this machine."""
    system = platform.system().lower()
    if system == "windows":
        return "win-x64"
    if system == "darwin":
        return "macos"
    if system == "linux":
        return "linux-x64"
    return system or "unknown"


def parse_version_parts(version: str) -> tuple[int, ...]:
    """Parse dotted versions, tolerating suffixes such as 0.2.0-qgis4."""
    if not version:
        return (0,)

    first_token = str(version).strip().split()[0]
    base_version = re.split(r"[-+]", first_token, maxsplit=1)[0]
    parts = [int(part) for part in re.findall(r"\d+", base_version)]
    return tuple(parts) if parts else (0,)


def compare_versions(left: str, right: str) -> int:
    """Compare two dotted version strings."""
    left_parts = parse_version_parts(left)
    right_parts = parse_version_parts(right)
    max_len = max(len(left_parts), len(right_parts))
    padded_left = left_parts + (0,) * (max_len - len(left_parts))
    padded_right = right_parts + (0,) * (max_len - len(right_parts))

    if padded_left < padded_right:
        return -1
    if padded_left > padded_right:
        return 1
    return 0


def read_metadata_field(metadata_path: Path, field_name: str) -> str:
    parser = ConfigParser()
    parser.optionxform = str
    parser.read(metadata_path, encoding="utf-8")
    if not parser.has_section("general"):
        return ""
    return parser.get("general", field_name, fallback="").strip()


def load_manifest(url: str = DEFAULT_MANIFEST_URL, timeout_seconds: int = 6) -> dict[str, Any]:
    request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
    with urllib.request.urlopen(request, timeout=timeout_seconds) as response:
        return json.loads(response.read().decode("utf-8"))


def _latest_version_from_manifest(manifest: dict[str, Any]) -> str:
    plugin = manifest.get("plugin")
    if isinstance(plugin, dict):
        version = plugin.get("version")
        if version:
            return str(version).strip()

    for key in ("latestVersion", "latest_version", "version"):
        version = manifest.get(key)
        if version:
            return str(version).strip()

    return ""


def _platform_entry(manifest: dict[str, Any], platform_id: str) -> dict[str, Any]:
    platforms = manifest.get("platforms")
    if not isinstance(platforms, dict):
        return {}

    entry = platforms.get(platform_id)
    return entry if isinstance(entry, dict) else {}


def _entry_url(entry: dict[str, Any], *names: str) -> Optional[str]:
    for name in names:
        value = entry.get(name)
        if value:
            return str(value).strip()
    return None


def evaluate_manifest(
    manifest: dict[str, Any],
    current_version: str,
    platform_id: Optional[str] = None,
) -> UpdateCheckResult:
    platform_value = platform_id or detect_platform_id()
    latest_version = _latest_version_from_manifest(manifest)
    download_page_url = str(
        manifest.get("downloadPageUrl")
        or manifest.get("download_page_url")
        or DEFAULT_DOWNLOAD_PAGE_URL
    )
    changelog = manifest.get("changelog")
    if changelog is not None:
        changelog = str(changelog)

    if not latest_version:
        return UpdateCheckResult(
            status="error",
            current_version=current_version,
            latest_version=None,
            platform_id=platform_value,
            message="The EMDS update manifest does not include a latest version.",
            download_page_url=download_page_url,
        )

    if compare_versions(current_version, latest_version) >= 0:
        return UpdateCheckResult(
            status="current",
            current_version=current_version,
            latest_version=latest_version,
            platform_id=platform_value,
            message=f"EMDS Task Workflows {current_version} is current.",
            download_page_url=download_page_url,
            changelog=changelog,
        )

    entry = _platform_entry(manifest, platform_value)
    if not entry:
        return UpdateCheckResult(
            status="platform_unavailable",
            current_version=current_version,
            latest_version=latest_version,
            platform_id=platform_value,
            message=(
                f"EMDS Task Workflows {latest_version} is available, but no "
                f"{platform_value} package is listed yet."
            ),
            download_page_url=download_page_url,
            changelog=changelog,
        )

    zip_url = _entry_url(entry, "zipUrl", "zip_url", "downloadUrl", "download_url")
    repository_url = _entry_url(entry, "repositoryUrl", "repository_url")

    return UpdateCheckResult(
        status="update_available",
        current_version=current_version,
        latest_version=latest_version,
        platform_id=platform_value,
        message=(
            f"EMDS Task Workflows {latest_version} is available "
            f"(installed: {current_version})."
        ),
        zip_url=zip_url,
        repository_url=repository_url,
        download_page_url=download_page_url,
        changelog=changelog,
    )


class UpdateChecker:
    """Fetch and evaluate the EMDS release manifest."""

    def __init__(
        self,
        metadata_path: Path,
        manifest_url: str = DEFAULT_MANIFEST_URL,
        platform_id: Optional[str] = None,
        timeout_seconds: int = 6,
    ):
        self.metadata_path = Path(metadata_path)
        self.manifest_url = manifest_url
        self.platform_id = platform_id or detect_platform_id()
        self.timeout_seconds = timeout_seconds

    def current_version(self) -> str:
        return read_metadata_field(self.metadata_path, "version") or "0.0.0"

    def check(self) -> UpdateCheckResult:
        current_version = self.current_version()
        try:
            manifest = load_manifest(self.manifest_url, self.timeout_seconds)
            if not isinstance(manifest, dict):
                raise ValueError("manifest root is not a JSON object")
        except (OSError, urllib.error.URLError, json.JSONDecodeError, ValueError) as exc:
            return UpdateCheckResult(
                status="error",
                current_version=current_version,
                latest_version=None,
                platform_id=self.platform_id,
                message=f"Could not check for EMDS updates: {exc}",
                download_page_url=DEFAULT_DOWNLOAD_PAGE_URL,
            )

        return evaluate_manifest(manifest, current_version, self.platform_id)
