"""
Client-side license validator — offline-first with periodic online checks.

Flow:
  1. On startup, load local license file
  2. Verify HMAC signature (tamper detection)
  3. Check expiry date
  4. Check hardware binding
  5. Periodically (every 24h) ping license server to check revocation
"""
from __future__ import annotations

import hashlib
import json
import os
import platform
import socket
import subprocess
import time
import urllib.request
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from license_server.config import (
    PUBLIC_KEY_BYTES,
    CLIENT_LICENSE_FILE,
    CLIENT_LICENSE_DIR,
    PRODUCT_ID,
    LICENSE_VERSION,
    TIERS,
)

# How often to check online for revocation (seconds)
ONLINE_CHECK_INTERVAL = 86400  # 24 hours
_LAST_AUTO_TRIAL_ERROR: dict[str, Any] | None = None
_CACHED_SECRETS: dict[str, Any] | None = None


def sync_remote_secrets() -> dict[str, Any]:
    """
    Fetch system secrets from the cloud license server.
    This ensures the client cannot function without a valid connection
    and a verified license state.
    """
    global _CACHED_SECRETS
    if _CACHED_SECRETS:
        return _CACHED_SECRETS

    # Path to local secrets cache
    from config_manager import get_app_dir
    cache_path = get_app_dir() / "system_secrets.cache.json"

    try:
        # 1. Try to download from cloud
        import requests
        # Server URL from config or hardcoded
        server_url = os.environ.get("GAT_LICENSE_SERVER", "https://license.goldauto.top")
        resp = requests.get(f"{server_url}/api/update/config/system_secrets.json", timeout=10)
        if resp.status_code == 200:
            data = resp.json().get("content", {})
            if data:
                # Atomically update cache
                cache_path.write_text(json.dumps(data), encoding="utf-8")
                _CACHED_SECRETS = data
                return data
    except Exception as e:
        print(f"[LICENSE] Failed to sync remote secrets: {e}")

    # 2. Fallback to cache
    if cache_path.exists():
        try:
            _CACHED_SECRETS = json.loads(cache_path.read_text(encoding="utf-8"))
            return _CACHED_SECRETS
        except:
            pass

    return {}


def _get_hardware_id() -> str:
    """Generate a stable hardware fingerprint."""
    parts = [
        platform.node() or "unknown-host",
        platform.machine() or "unknown-arch",
    ]
    # Try to get MAC address for additional uniqueness
    try:
        mac = uuid.getnode()
        parts.append(str(mac))
    except Exception:
        pass
    raw = "|".join(parts)
    return hashlib.sha256(raw.encode()).hexdigest()[:16]


def _auto_trial() -> dict[str, Any] | None:
    """Try to auto-claim a 14-day trial license. Returns license dict or None."""
    global _LAST_AUTO_TRIAL_ERROR
    try:
        from src.license.trial import claim_trial
        result = claim_trial()
        if result.get("ok"):
            _LAST_AUTO_TRIAL_ERROR = None
            # Re-load the newly created license
            return load_license(auto_trial=False)
        _LAST_AUTO_TRIAL_ERROR = {
            "status": str(result.get("error", "TRIAL_CLAIM_FAILED") or "TRIAL_CLAIM_FAILED"),
            "message": str(result.get("message", "试用授权失败") or "试用授权失败"),
        }
    except Exception as exc:
        _LAST_AUTO_TRIAL_ERROR = {
            "status": "TRIAL_AUTO_CLAIM_EXCEPTION",
            "message": "Automatic trial activation failed unexpectedly.",
        }
    return None


def _verify_signature(payload: dict[str, Any], signature: str) -> bool:
    """Verify Ed25519 signature of a license payload (server-only signing)."""
    import base64
    from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
    try:
        public_key = Ed25519PublicKey.from_public_bytes(PUBLIC_KEY_BYTES)
        data = json.dumps(payload, sort_keys=True, ensure_ascii=False).encode()
        public_key.verify(base64.b64decode(signature), data)
        return True
    except Exception:
        return False


def _refresh_trial_if_needed(payload: dict[str, Any], auto_trial: bool) -> dict[str, Any] | None:
    if auto_trial and payload.get("is_trial"):
        return _auto_trial()
    return None


def load_license(auto_trial: bool = True) -> dict[str, Any] | None:
    """Load and verify the local license file. Returns None if invalid/missing.

    If auto_trial=True and no license exists, automatically claims a 14-day trial.
    """
    if not CLIENT_LICENSE_FILE.exists():
        if auto_trial:
            return _auto_trial()
        return None
    try:
        raw = json.loads(CLIENT_LICENSE_FILE.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        if auto_trial:
            return _auto_trial()
        return None

    signed = raw.get("signed_license", {})
    payload = signed.get("payload", {})
    signature = signed.get("signature", "")

    if not payload or not signature:
        refreshed = _refresh_trial_if_needed(payload, auto_trial)
        if refreshed:
            return refreshed
        return None

    # Verify signature (tamper detection)
    if not _verify_signature(payload, signature):
        refreshed = _refresh_trial_if_needed(payload, auto_trial)
        if refreshed:
            return refreshed
        return None

    # Verify product
    if payload.get("product") != PRODUCT_ID:
        return None

    # Verify version
    if payload.get("version", 0) != LICENSE_VERSION:
        return None

    # Check expiry
    expires_at = payload.get("expires_at", "")
    if expires_at:
        try:
            exp = datetime.fromisoformat(expires_at)
            if datetime.now(timezone.utc) > exp:
                return {"error": "LICENSE_EXPIRED", "payload": payload}
        except ValueError:
            pass

    # Check hardware binding
    current_hwid = _get_hardware_id()
    bound_hwid = payload.get("hardware_id", "")
    if bound_hwid and bound_hwid != current_hwid:
        # ── Trial license: auto-reclaim to rebind hardware ──
        if payload.get("is_trial") and auto_trial:
            refreshed = _auto_trial()
            if refreshed:
                return refreshed
        return {"error": "HARDWARE_MISMATCH", "payload": payload,
                "current_hwid": current_hwid, "bound_hwid": bound_hwid}

    return {"payload": payload, "valid": True}


def save_license(signed_license: dict[str, Any]) -> bool:
    """Save a signed license to disk after successful activation."""
    CLIENT_LICENSE_DIR.mkdir(parents=True, exist_ok=True)
    try:
        CLIENT_LICENSE_FILE.write_text(
            json.dumps({"signed_license": signed_license}, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
        # Set restrictive permissions
        os.chmod(CLIENT_LICENSE_FILE, 0o600)
        return True
    except OSError:
        return False


def get_license_status() -> dict[str, Any]:
    """Get human-readable license status."""
    lic = load_license()
    if not lic:
        if _LAST_AUTO_TRIAL_ERROR:
            return dict(_LAST_AUTO_TRIAL_ERROR)
        return {"status": "UNLICENSED", "message": "No valid license found."}
    if lic.get("error"):
        return {"status": lic["error"], "message": _error_message(lic["error"])}

    payload = lic["payload"]
    tier = payload.get("tier", "unknown")
    features = TIERS.get(tier, {}).get("features", [])
    expires = payload.get("expires_at", "")[:10] if payload.get("expires_at") else "never"

    return {
        "status": "ACTIVE",
        "tier": tier,
        "email": payload.get("customer_email", ""),
        "expires": expires,
        "features": features,
        "hardware_id": payload.get("hardware_id", ""),
    }


def _error_message(error: str) -> str:
    messages = {
        "LICENSE_EXPIRED": "Your license has expired. Please renew to continue using Gold AutoTrade.",
        "HARDWARE_MISMATCH": "License is bound to a different machine. Please re-activate.",
        "LICENSE_REVOKED": "This license has been revoked. Please contact support.",
    }
    return messages.get(error, f"License error: {error}")


def check_online(server_url: str) -> dict[str, Any]:
    """Periodic online check — returns True if license is still valid."""
    try:
        import urllib.request

        lic = load_license()
        if not lic or not lic.get("valid"):
            return {"ok": False, "error": "NO_VALID_LICENSE"}

        payload = lic["payload"]
        body = json.dumps({
            "license_key": payload.get("license_key", ""),
            "hardware_id": _get_hardware_id(),
        }).encode()

        req = urllib.request.Request(
            f"{server_url}/validate",
            data=body,
            headers={"Content-Type": "application/json"},
        )
        with urllib.request.urlopen(req, timeout=10) as resp:
            result = json.loads(resp.read().decode())

        if result.get("ok"):
            # Update local license with fresh signature
            if "signed_license" in result:
                save_license(result["signed_license"])
            return {"ok": True}

        return {"ok": False, "error": result.get("error", "UNKNOWN")}

    except Exception as e:
        # Network error — allow offline use, don't block
        return {"ok": True, "warning": f"Online check failed: {e}"}
