from __future__ import annotations

import json
import sys
import uuid
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.parse import urljoin
from urllib.request import Request

from app_core.http_client import open_url


def _build_default_server_settings() -> dict:
    if sys.platform == "darwin":
        base_url = "https://texmind.xyz"
        return {
            "data_mode": "online",
            "api_base_url": base_url,
            "shared_files_base_url": f"{base_url}/shared",
            "healthcheck_path": "/health",
            "update_manifest_url": f"{base_url}/manifests/desktop.json",
            "device_name": "",
            "device_id": "",
            "auto_check_updates": True,
            "request_timeout_seconds": 8,
        }
    return {
        "data_mode": "local",
        "api_base_url": "http://127.0.0.1:8000",
        "shared_files_base_url": "http://127.0.0.1:8000/shared",
        "healthcheck_path": "/health",
        "update_manifest_url": "http://127.0.0.1:8000/manifests/desktop.json",
        "device_name": "",
        "device_id": "",
        "auto_check_updates": True,
        "request_timeout_seconds": 8,
    }


DEFAULT_SERVER_SETTINGS = _build_default_server_settings()


def load_server_settings(path: Path) -> dict:
    payload = dict(DEFAULT_SERVER_SETTINGS)
    if path.exists():
        try:
            loaded = json.loads(path.read_text(encoding="utf-8"))
            if isinstance(loaded, dict):
                for key, value in loaded.items():
                    if key in payload:
                        if key == "data_mode" and str(value or "").strip() not in {"local", "online"}:
                            continue
                        if key in {"api_base_url", "shared_files_base_url", "update_manifest_url"}:
                            if not str(value or "").strip():
                                continue
                        payload[key] = value
        except Exception:
            pass

    if not str(payload.get("device_id", "")).strip():
        payload["device_id"] = str(uuid.uuid4())
    if not str(payload.get("device_name", "")).strip():
        payload["device_name"] = "desktop-client"
    return payload


def save_server_settings(path: Path, payload: dict) -> dict:
    normalized = load_server_settings(path)
    for key in DEFAULT_SERVER_SETTINGS:
        if key in payload:
            normalized[key] = payload[key]
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(normalized, ensure_ascii=False, indent=2), encoding="utf-8")
    return normalized


def build_healthcheck_url(settings: dict) -> str:
    base_url = str(settings.get("api_base_url", "")).strip()
    healthcheck_path = str(settings.get("healthcheck_path", "/health")).strip() or "/health"
    if not base_url:
        return ""
    if not healthcheck_path.startswith("/"):
        healthcheck_path = "/" + healthcheck_path
    return urljoin(base_url.rstrip("/") + "/", healthcheck_path.lstrip("/"))


def compare_versions(left: str, right: str) -> int:
    def parse_parts(raw: str) -> list[int]:
        parts: list[int] = []
        for part in raw.strip().split("."):
            digits = "".join(ch for ch in part if ch.isdigit())
            parts.append(int(digits) if digits else 0)
        return parts

    left_parts = parse_parts(left)
    right_parts = parse_parts(right)
    max_len = max(len(left_parts), len(right_parts))
    left_parts.extend([0] * (max_len - len(left_parts)))
    right_parts.extend([0] * (max_len - len(right_parts)))

    if left_parts < right_parts:
        return -1
    if left_parts > right_parts:
        return 1
    return 0


def _fetch_json(url: str, timeout_seconds: int) -> dict:
    request = Request(url, headers={"User-Agent": "CitiFoxERPApp/desktop"})
    with open_url(request, timeout=max(1, int(timeout_seconds))) as response:
        charset = response.headers.get_content_charset() or "utf-8"
        payload = json.loads(response.read().decode(charset, errors="replace"))
        if not isinstance(payload, dict):
            raise ValueError("Response JSON must be an object.")
        return payload


def test_server_connection(settings: dict) -> dict:
    url = build_healthcheck_url(settings)
    timeout_seconds = int(settings.get("request_timeout_seconds", 8) or 8)
    if not url:
        return {
            "ok": False,
            "url": "",
            "message": "API Base URL is empty.",
        }

    try:
        payload = _fetch_json(url, timeout_seconds)
        return {
            "ok": True,
            "url": url,
            "message": f"Connected successfully. Health payload keys: {', '.join(sorted(payload.keys())) or '(empty)'}",
            "payload": payload,
        }
    except HTTPError as exc:
        return {
            "ok": False,
            "url": url,
            "message": f"HTTP error {exc.code}: {exc.reason}",
        }
    except URLError as exc:
        return {
            "ok": False,
            "url": url,
            "message": f"Connection failed: {exc.reason}",
        }
    except Exception as exc:
        return {
            "ok": False,
            "url": url,
            "message": f"Connection failed: {exc}",
        }


def check_update_manifest(settings: dict, current_version: str) -> dict:
    manifest_url = str(settings.get("update_manifest_url", "")).strip()
    timeout_seconds = int(settings.get("request_timeout_seconds", 8) or 8)
    if not manifest_url:
        return {
            "ok": False,
            "update_available": False,
            "message": "Update Manifest URL is empty.",
        }

    try:
        payload = _fetch_json(manifest_url, timeout_seconds)
        remote_version = str(payload.get("version", "")).strip()
        if not remote_version:
            raise ValueError("Manifest is missing the 'version' field.")

        update_available = compare_versions(current_version, remote_version) < 0
        download_url = str(payload.get("download_url", "")).strip()
        if download_url:
            download_url = urljoin(manifest_url, download_url)
        notes = str(payload.get("notes", "")).strip()
        message = (
            f"New version available: {remote_version}"
            if update_available
            else f"Current version {current_version} is up to date."
        )
        return {
            "ok": True,
            "update_available": update_available,
            "current_version": current_version,
            "remote_version": remote_version,
            "download_url": download_url,
            "notes": notes,
            "message": message,
            "payload": payload,
        }
    except Exception as exc:
        return {
            "ok": False,
            "update_available": False,
            "message": f"Unable to read update manifest: {exc}",
        }
