"""
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 hmac
import json
import os
import platform
import socket
import subprocess
import time
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from license_server.config import (
    SIGNING_SECRET,
    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


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."""
    try:
        from src.license.trial import claim_trial
        result = claim_trial()
        if result.get("ok"):
            # Re-load the newly created license
            return load_license(auto_trial=False)
    except Exception:
        pass
    return None


def _verify_signature(payload: dict[str, Any], signature: str) -> bool:
    """Verify HMAC-SHA256 signature of a license payload."""
    data = json.dumps(payload, sort_keys=True, ensure_ascii=False).encode()
    expected = hmac.new(SIGNING_SECRET.encode(), data, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)


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):
        return None

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

    if not payload or not signature:
        return None

    # Verify signature (tamper detection)
    if not _verify_signature(payload, signature):
        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:
        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:
        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}"}
