"""Golem MCP license system — offline verification via HMAC-SHA256."""

import hashlib
import hmac
import json
import os
import re
from pathlib import Path

# License key format: GOLEM1-{order_id}-{signature_8chars}
# Example: GOLEM1-GUM-12345-A3F2B1C9

KEY_PATTERN = re.compile(r"^GOLEM1-([A-Z0-9-]+)-([A-F0-9]{8})$")
KEY_PREFIX = "GOLEM1"

# Trial mode configuration
TRIAL_LIMIT = 10  # Number of free MCP tool calls

# Secret for signing keys — in production, this is embedded at build time
# In dev, use GOLEM_LICENSE_SECRET env var
_DEFAULT_SECRET = "golem-mcp-2024-release-key"


def _get_secret() -> bytes:
    """Get the signing secret."""
    secret = os.environ.get("GOLEM_LICENSE_SECRET", _DEFAULT_SECRET)
    return secret.encode("utf-8")


def _compute_signature(order_id: str) -> str:
    """Compute HMAC-SHA256 signature for an order ID."""
    secret = _get_secret()
    message = f"{KEY_PREFIX}-{order_id}".encode("utf-8")
    sig = hmac.new(secret, message, hashlib.sha256).hexdigest()[:8].upper()
    return sig


def generate_key(order_id: str) -> str:
    """Generate a signed license key for an order ID.

    Args:
        order_id: Gumroad order ID or similar identifier (e.g., "GUM-12345")

    Returns:
        License key in format GOLEM1-{order_id}-{signature}
    """
    order_id = order_id.upper().strip()
    if not order_id:
        raise ValueError("Order ID cannot be empty")
    signature = _compute_signature(order_id)
    return f"{KEY_PREFIX}-{order_id}-{signature}"


def verify_key(key: str) -> bool:
    """Verify a license key is valid.

    Args:
        key: License key to verify

    Returns:
        True if valid, False otherwise
    """
    if not key:
        return False

    key = key.strip().upper()
    match = KEY_PATTERN.match(key)
    if not match:
        return False

    order_id = match.group(1)
    provided_sig = match.group(2)
    expected_sig = _compute_signature(order_id)

    return hmac.compare_digest(provided_sig, expected_sig)


def get_license_path() -> Path:
    """Get the path to the license file."""
    golem_dir = Path.home() / ".golem"
    return golem_dir / "license.key"


def load_license() -> str | None:
    """Load the stored license key.

    Returns:
        License key string if exists and readable, None otherwise
    """
    path = get_license_path()
    if not path.exists():
        return None
    try:
        return path.read_text().strip()
    except OSError:
        return None


def save_license(key: str) -> None:
    """Store a license key.

    Args:
        key: License key to store

    Raises:
        OSError: If unable to write the file
    """
    path = get_license_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(key.strip() + "\n")


def is_licensed() -> bool:
    """Check if a valid license is installed.

    Returns:
        True if a valid license is stored, False otherwise
    """
    key = load_license()
    if not key:
        return False
    return verify_key(key)


def activate(key: str) -> tuple[bool, str]:
    """Attempt to activate a license key.

    Args:
        key: License key to activate

    Returns:
        Tuple of (success, message)
    """
    if not key or not key.strip():
        return False, "No license key provided"

    key = key.strip().upper()

    if not verify_key(key):
        return False, "Invalid license key"

    try:
        save_license(key)
    except OSError as e:
        return False, f"Failed to save license: {e}"

    return True, "License activated successfully"


# ══════════════════════════════════════════════════════════════════════════════
# Trial mode
# ══════════════════════════════════════════════════════════════════════════════

def _get_usage_path() -> Path:
    """Get the path to the usage tracking file."""
    golem_dir = Path.home() / ".golem"
    return golem_dir / "usage.json"


def _load_usage() -> dict:
    """Load usage data from file."""
    path = _get_usage_path()
    if not path.exists():
        return {"calls": 0}
    try:
        return json.loads(path.read_text())
    except (json.JSONDecodeError, OSError):
        return {"calls": 0}


def _save_usage(data: dict) -> None:
    """Save usage data to file."""
    path = _get_usage_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2) + "\n")


def get_trial_status() -> dict:
    """Get current trial status.

    Returns:
        Dict with 'calls', 'limit', 'remaining', 'expired'
    """
    usage = _load_usage()
    calls = usage.get("calls", 0)
    remaining = max(0, TRIAL_LIMIT - calls)
    return {
        "calls": calls,
        "limit": TRIAL_LIMIT,
        "remaining": remaining,
        "expired": remaining == 0,
    }


def increment_trial_usage() -> dict:
    """Increment trial usage counter.

    Returns:
        Updated trial status dict
    """
    usage = _load_usage()
    usage["calls"] = usage.get("calls", 0) + 1
    _save_usage(usage)
    return get_trial_status()


def can_use_trial() -> bool:
    """Check if trial mode is available (calls remaining).

    Returns:
        True if trial calls remaining, False otherwise
    """
    status = get_trial_status()
    return not status["expired"]


def reset_trial() -> None:
    """Reset trial usage counter (admin use)."""
    _save_usage({"calls": 0})


def is_allowed() -> tuple[bool, str]:
    """Check if usage is allowed (licensed or trial available).

    Returns:
        Tuple of (allowed, mode) where mode is 'licensed', 'trial', or 'blocked'
    """
    if is_licensed():
        return True, "licensed"
    if can_use_trial():
        return True, "trial"
    return False, "blocked"


# CLI helper for generating keys (admin use only)
if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print("Usage: python golem_license.py <order_id>")
        print("  Generates a license key for the given order ID")
        sys.exit(1)

    order_id = sys.argv[1]
    key = generate_key(order_id)
    print(f"Generated key: {key}")
    print(f"Verification: {'VALID' if verify_key(key) else 'INVALID'}")
