"""
Agent license token management for g-gremlin CLI.

The license token is a JWT issued by FoundryOps that:
- Is cached locally at ~/.g_gremlin/license.jwt
- Expires every 6 hours (CLI refreshes proactively)
- Contains tier, trial status, and feature flags
- Is verified locally by backend APIs (no DB lookup per request)
"""

from __future__ import annotations

import json
import os
import sys
import time
from dataclasses import dataclass, field
from functools import wraps
from pathlib import Path
from typing import Any, Callable, Dict, Optional, TypeVar

import httpx

from g_gremlin import dev_mode
from g_gremlin.paths import gremlin_path

# FoundryOps API base URL
FOUNDRYOPS_API_URL = (
    os.getenv("FOUNDRYOPS_API_URL")
    or os.getenv("FOUNDRYMATCH_API_URL")
    or "https://api.foundryops.io"
)

# Token file locations (dynamic to respect runtime G_GREMLIN_HOME changes).
def _dynamic_path_override(name: str) -> Path | None:
    value = globals().get(name)
    if isinstance(value, Path):
        return value
    if isinstance(value, str) and value.strip():
        return Path(value)
    return None


def _license_token_path() -> Path:
    return _dynamic_path_override("LICENSE_TOKEN_PATH") or gremlin_path("license.jwt")


def _license_meta_path() -> Path:
    return _dynamic_path_override("LICENSE_META_PATH") or gremlin_path("license_meta.json")


def _refresh_token_path() -> Path:
    return _dynamic_path_override("REFRESH_TOKEN_PATH") or gremlin_path("refresh_token")


def __getattr__(name: str) -> Any:
    if name == "LICENSE_TOKEN_PATH":
        return _license_token_path()
    if name == "LICENSE_META_PATH":
        return _license_meta_path()
    if name == "REFRESH_TOKEN_PATH":
        return _refresh_token_path()
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

# Refresh token when less than 1 hour remaining
TOKEN_REFRESH_THRESHOLD_SECONDS = 3600


def _default_local_free() -> tuple[Dict[str, bool], Dict[str, int | None]]:
    """Fallback local-free defaults if config cannot be loaded."""
    return (
        {
            "local_tools": True,
            "help_json": True,
            "google_read": True,
            "google_write": False,
            "workspace_sinks": False,
            "foundrygraph": False,
            "cloud_fuzzymatch": False,
            "notify_webhook": False,
            "s3_export": False,
            "hubspot_admin": False,
            "multi_crm": False,
            "enrichment_apply": False,
            "sequencer_enroll_apply": False,
            "autopilot_apply": False,
            "scheduling": False,
            "notify_advanced": False,
        },
        {
            "google_write_ops_per_day": 0,
            "cells_per_push_max": 0,
            "fg_rows_per_month": 0,
            "cloud_fuzzymatch_rows_per_month": 0,
            "webhook_events_per_day": 0,
            "s3_exports_per_day": 0,
            "hubspot_portals_max": 0,
            "hubspot_pack_applies_per_month": 0,
            "receipt_retention_days": 7,
        },
    )


def _tier_defaults(tier: str) -> tuple[Dict[str, bool], Dict[str, int | None]]:
    try:
        from g_gremlin.credentials.entitlements import get_cli_entitlements_config

        config = get_cli_entitlements_config()
        return config.get_tier_defaults(tier)
    except Exception:
        if tier == "community":
            return _default_local_free()
        try:
            from g_gremlin.credentials.entitlements import get_cli_entitlements_config

            config = get_cli_entitlements_config()
            return config.get_tier_defaults("platform")
        except Exception:
            return _default_local_free()


_TRIAL_TIERS = frozenset({"agent_trial", "trial"})


def _is_trial_tier(tier: str | None) -> bool:
    """Return True if *tier* represents any trial plan variant."""
    return (tier or "").lower() in _TRIAL_TIERS


def _legacy_jwt_plan(tier: str | None) -> str:
    """Map legacy JWT tier strings onto known CLI plans conservatively."""
    normalized = (tier or "").strip().lower()
    if not normalized:
        return "community"
    # Current tier names pass through directly
    if normalized in {"community", "pro", "crm_engineer", "platform", "enterprise"}:
        return normalized
    # Legacy tier names → new equivalents
    _LEGACY_MAP = {
        "local_free": "community",
        "workspace_ops": "pro",
        "hubspot_admin": "crm_engineer",
        "full_stack": "platform",
    }
    if normalized in _LEGACY_MAP:
        return _LEGACY_MAP[normalized]
    # Full-stack trials are metered but should expose paid command surfaces.
    if normalized in _TRIAL_TIERS:
        return "platform"
    if normalized in {"free", "agent_free"}:
        return "community"
    if normalized in {"agent_paid", "paid", "scale"}:
        return "platform"
    return "community"


def _iso_to_unix(ts: str | None) -> int:
    if not ts:
        return 0
    try:
        from datetime import datetime, timezone

        parsed = datetime.fromisoformat(ts.replace("Z", "+00:00"))
        if parsed.tzinfo is None:
            parsed = parsed.replace(tzinfo=timezone.utc)
        return int(parsed.timestamp())
    except Exception:
        return 0


@dataclass
class LicenseInfo:
    """Decoded license information."""

    tier: str  # 'agent_trial' or 'agent_paid'
    status: str  # 'active', 'expired', 'canceled'
    trial_ends_at: Optional[str]  # ISO timestamp
    days_remaining: Optional[int]
    features: list[str]
    token_expires_at: int  # Unix timestamp


@dataclass
class LicenseCheckResult:
    """Result of a license check."""

    valid: bool
    license: Optional[LicenseInfo]
    error: Optional[str]
    warnings: list[str]
    plan: str | None = None
    trial: bool = False
    source: str = "jwt"
    license_id: str | None = None
    account_id: str | None = None
    issued_at: str | None = None
    expires_at: str | None = None
    grace_period_hours: int = 0
    schema_version: int | None = None
    issuer: str | None = None
    kid: str | None = None
    entitlements_hash: str | None = None
    entitlements: Dict[str, bool] = field(default_factory=dict)
    limits: Dict[str, int | None] = field(default_factory=dict)


def _ensure_dir():
    """Ensure ~/.g_gremlin directory exists."""
    _license_token_path().parent.mkdir(parents=True, exist_ok=True)


def _read_token() -> Optional[str]:
    """Read cached token from disk."""
    token_path = _license_token_path()
    if not token_path.exists():
        return None
    try:
        return token_path.read_text().strip()
    except Exception:
        return None


def _read_meta() -> Dict[str, Any]:
    """Read token metadata from disk."""
    meta_path = _license_meta_path()
    if not meta_path.exists():
        return {}
    try:
        return json.loads(meta_path.read_text())
    except Exception:
        return {}


def _try_chmod_600(path: Path) -> None:
    # Best-effort only. On Windows this may be a no-op depending on filesystem.
    try:
        os.chmod(path, 0o600)
    except Exception:
        pass


def _atomic_write_text(path: Path, content: str) -> None:
    _ensure_dir()
    tmp_path = path.with_name(f".{path.name}.{os.getpid()}.tmp")
    try:
        tmp_path.write_text(content, encoding="utf-8")
        os.replace(tmp_path, path)
    finally:
        try:
            tmp_path.unlink(missing_ok=True)
        except Exception:
            pass


def _write_token(token: str, meta: Dict[str, Any]):
    """Write token and metadata to disk (atomic replace)."""
    token_path = _license_token_path()
    meta_path = _license_meta_path()
    _atomic_write_text(token_path, token)
    _atomic_write_text(meta_path, json.dumps(meta, indent=2))
    _try_chmod_600(token_path)
    _try_chmod_600(meta_path)


def _clear_token():
    """Remove cached token."""
    token_path = _license_token_path()
    meta_path = _license_meta_path()
    try:
        token_path.unlink(missing_ok=True)
        meta_path.unlink(missing_ok=True)
    except Exception:
        pass
    _clear_refresh_token()


def _read_refresh_token() -> Optional[str]:
    refresh_path = _refresh_token_path()
    if not refresh_path.exists():
        return None
    try:
        return refresh_path.read_text(encoding="utf-8").strip()
    except Exception:
        return None


def _write_refresh_token(token: str) -> None:
    refresh_path = _refresh_token_path()
    _atomic_write_text(refresh_path, token.strip())
    _try_chmod_600(refresh_path)


def _clear_refresh_token() -> None:
    refresh_path = _refresh_token_path()
    try:
        refresh_path.unlink(missing_ok=True)
    except Exception:
        pass


def _decode_jwt_payload(token: str) -> Optional[Dict[str, Any]]:
    """Decode JWT payload without verification (for reading expiry)."""
    import base64

    try:
        parts = token.split(".")
        if len(parts) != 3:
            return None

        # Add padding if needed
        payload_b64 = parts[1]
        padding = 4 - len(payload_b64) % 4
        if padding != 4:
            payload_b64 += "=" * padding

        payload_json = base64.urlsafe_b64decode(payload_b64)
        return json.loads(payload_json)
    except Exception:
        return None


def _token_needs_refresh(meta: Dict[str, Any]) -> bool:
    """Check if token needs refresh (expires in < 1 hour)."""
    expires_at = meta.get("expires_at", 0)
    now = int(time.time())
    return (expires_at - now) < TOKEN_REFRESH_THRESHOLD_SECONDS


def refresh_license_token(auth_token: str) -> LicenseCheckResult:
    """
    Refresh license token from FoundryOps API.

    Args:
        auth_token: Clerk session token or API key for authentication

    Returns:
        LicenseCheckResult with new token info
    """
    try:
        response = httpx.post(
            f"{FOUNDRYOPS_API_URL}/api/v1/agent/token",
            headers={"Authorization": f"Bearer {auth_token}"},
            timeout=30.0,
        )

        if response.status_code == 403:
            data = response.json()
            error = data.get("error", "UNKNOWN")
            message = data.get("message", "License validation failed")

            # Clear cached token on explicit failures
            if error in ("NO_LICENSE", "TRIAL_EXPIRED", "LICENSE_INACTIVE"):
                _clear_token()

            return LicenseCheckResult(
                valid=False,
                license=None,
                error=message,
                warnings=[],
            )

        if response.status_code != 200:
            return LicenseCheckResult(
                valid=False,
                license=None,
                error=f"API error: {response.status_code}",
                warnings=[],
            )

        data = response.json()

        if not data.get("valid"):
            return LicenseCheckResult(
                valid=False,
                license=None,
                error=data.get("error", "Unknown error"),
                warnings=[],
            )

        # Extract token and license info
        token = data.get("token")
        refresh_token = data.get("refresh_token")
        license_data = data.get("license", {})
        warnings = license_data.get("warnings", [])

        # Decode token to get expiry
        payload = _decode_jwt_payload(token) if token else None
        expires_at = payload.get("exp", 0) if payload else 0

        # Build license info
        license_info = LicenseInfo(
            tier=license_data.get("type", "unknown"),
            status=license_data.get("status", "unknown"),
            trial_ends_at=license_data.get("trialEndsAt"),
            days_remaining=license_data.get("daysRemaining"),
            features=payload.get("features", []) if payload else [],
            token_expires_at=expires_at,
        )

        # Cache the token
        if token:
            _write_token(
                token,
                {
                    "expires_at": expires_at,
                    "tier": license_info.tier,
                    "status": license_info.status,
                    "trial_ends_at": license_info.trial_ends_at,
                    "days_remaining": license_info.days_remaining,
                    "refreshed_at": int(time.time()),
                },
            )

        if isinstance(refresh_token, str) and refresh_token.strip():
            _write_refresh_token(refresh_token)

        return LicenseCheckResult(
            valid=True,
            license=license_info,
            error=None,
            warnings=warnings,
        )

    except httpx.TimeoutException:
        return LicenseCheckResult(
            valid=False,
            license=None,
            error="Connection timeout. Check your internet connection.",
            warnings=[],
        )
    except Exception as e:
        return LicenseCheckResult(
            valid=False,
            license=None,
            error=f"Failed to validate license: {e}",
            warnings=[],
        )


def cache_license_token(
    token: str,
    license_data: Optional[Dict[str, Any]] = None,
    refresh_token: Optional[str] = None,
) -> LicenseCheckResult:
    """
    Cache an already-issued agent license JWT locally.

    Used by browser/device auth flows that return a signed license token
    directly to the CLI (instead of exchanging an auth token).
    """
    payload = _decode_jwt_payload(token)
    if not payload:
        return LicenseCheckResult(
            valid=False,
            license=None,
            error="Invalid license token payload.",
            warnings=[],
        )

    now = int(time.time())
    expires_at = int(payload.get("exp", 0) or 0)
    if expires_at <= now:
        return LicenseCheckResult(
            valid=False,
            license=None,
            error="License token is already expired.",
            warnings=[],
        )

    data = license_data or {}
    tier = str(payload.get("tier") or data.get("type") or "unknown")
    status = str(data.get("status") or "active")
    trial_ends_at = data.get("trialEndsAt") or payload.get("trial_ends_at")

    days_remaining_raw = data.get("daysRemaining")
    days_remaining: Optional[int]
    if isinstance(days_remaining_raw, int):
        days_remaining = days_remaining_raw
    else:
        days_remaining = None

    warnings_raw = data.get("warnings") if isinstance(data, dict) else None
    warnings = [str(w) for w in warnings_raw] if isinstance(warnings_raw, list) else []

    _write_token(
        token,
        {
            "expires_at": expires_at,
            "tier": tier,
            "status": status,
            "trial_ends_at": trial_ends_at,
            "days_remaining": days_remaining,
            "refreshed_at": now,
        },
    )

    if isinstance(refresh_token, str) and refresh_token.strip():
        _write_refresh_token(refresh_token)

    return LicenseCheckResult(
        valid=True,
        license=LicenseInfo(
            tier=tier,
            status=status,
            trial_ends_at=trial_ends_at,
            days_remaining=days_remaining,
            features=payload.get("features", []) if isinstance(payload.get("features"), list) else [],
            token_expires_at=expires_at,
        ),
        error=None,
        warnings=warnings,
    )


def attempt_silent_refresh() -> LicenseCheckResult:
    refresh_token = _read_refresh_token()
    if not refresh_token or not refresh_token.startswith("grt_"):
        return LicenseCheckResult(
            valid=False,
            license=None,
            error="No refresh token found. Run 'g-gremlin auth login' to authenticate.",
            warnings=[],
        )

    try:
        response = httpx.post(
            f"{FOUNDRYOPS_API_URL}/api/v1/agent/token/refresh",
            json={"refresh_token": refresh_token},
            timeout=30.0,
        )

        if response.status_code == 401:
            _clear_refresh_token()
            return LicenseCheckResult(
                valid=False,
                license=None,
                error="Session expired. Run 'g-gremlin auth login' to re-authenticate.",
                warnings=[],
            )

        if response.status_code == 428:
            data = response.json()
            accept_url = str(data.get("acceptUrl") or "").strip()
            if accept_url.startswith("/"):
                accept_url = f"{FOUNDRYOPS_API_URL}{accept_url}"
            message = str(data.get("message") or "Please accept terms to continue.")
            if accept_url:
                message = f"{message} Accept here: {accept_url}"
            return LicenseCheckResult(
                valid=False,
                license=None,
                error=message,
                warnings=[],
            )

        if response.status_code == 403:
            data = response.json()
            message = str(data.get("message") or "License validation failed")
            _clear_token()
            return LicenseCheckResult(
                valid=False,
                license=None,
                error=message,
                warnings=[],
            )

        if response.status_code != 200:
            return LicenseCheckResult(
                valid=False,
                license=None,
                error=f"API error: {response.status_code}",
                warnings=[],
            )

        data = response.json()
        if not data.get("valid"):
            return LicenseCheckResult(
                valid=False,
                license=None,
                error=str(data.get("error") or "Unknown error"),
                warnings=[],
            )

        token = data.get("token")
        license_data = data.get("license", {})
        warnings = license_data.get("warnings", [])

        payload = _decode_jwt_payload(token) if token else None
        expires_at = payload.get("exp", 0) if payload else 0

        license_info = LicenseInfo(
            tier=license_data.get("type", "unknown"),
            status=license_data.get("status", "unknown"),
            trial_ends_at=license_data.get("trialEndsAt"),
            days_remaining=license_data.get("daysRemaining"),
            features=payload.get("features", []) if payload else [],
            token_expires_at=expires_at,
        )

        if token:
            _write_token(
                token,
                {
                    "expires_at": expires_at,
                    "tier": license_info.tier,
                    "status": license_info.status,
                    "trial_ends_at": license_info.trial_ends_at,
                    "days_remaining": license_info.days_remaining,
                    "refreshed_at": int(time.time()),
                },
            )

        new_refresh_token = data.get("refresh_token")
        if isinstance(new_refresh_token, str) and new_refresh_token.strip():
            _write_refresh_token(new_refresh_token)

        return LicenseCheckResult(
            valid=True,
            license=license_info,
            error=None,
            warnings=warnings,
        )

    except httpx.TimeoutException:
        return LicenseCheckResult(
            valid=False,
            license=None,
            error="Connection timeout. Check your internet connection.",
            warnings=[],
        )
    except Exception as e:
        return LicenseCheckResult(
            valid=False,
            license=None,
            error=f"Failed to refresh license: {e}",
            warnings=[],
        )


def get_cached_license() -> Optional[LicenseInfo]:
    """Get license info from cached token without API call."""
    token = _read_token()
    meta = _read_meta()

    if not token:
        return None

    # Check if token is expired
    expires_at = meta.get("expires_at", 0)
    if expires_at <= int(time.time()):
        return None

    return LicenseInfo(
        tier=meta.get("tier", "unknown"),
        status=meta.get("status", "unknown"),
        trial_ends_at=meta.get("trial_ends_at"),
        days_remaining=meta.get("days_remaining"),
        features=[],
        token_expires_at=expires_at,
    )


def get_license_token() -> Optional[str]:
    """Get the cached license token for API requests."""
    token = _read_token()
    meta = _read_meta()

    if not token:
        return None

    # Check if token is expired
    expires_at = meta.get("expires_at", 0)
    if expires_at <= int(time.time()):
        return None

    return token


def check_license(auth_token: Optional[str] = None) -> LicenseCheckResult:
    """
    Check license status, refreshing if needed.

    Args:
        auth_token: Optional auth token for refresh. If not provided and
                   refresh is needed, returns cached info with warning.

    Returns:
        LicenseCheckResult with current license status
    """
    # Dev mode bypass - unlimited access for local development
    if dev_mode.is_dev_mode_enabled():
        return LicenseCheckResult(
            valid=True,
            license=LicenseInfo(
                tier="dev_unlimited",
                status="active",
                trial_ends_at=None,
                days_remaining=None,
                features=["foundrygraph", "docs", "all"],
                token_expires_at=int(time.time()) + 86400 * 365,  # 1 year
            ),
            error=None,
            warnings=["[DEV MODE] License checks bypassed"],
        )

    token = _read_token()
    meta = _read_meta()
    now = int(time.time())
    expires_at = int(meta.get("expires_at", 0) or 0)

    # No cached token - need to authenticate.
    if not token:
        # NEW: try refresh-token exchange first (covers "license.jwt deleted" cases)
        silent = attempt_silent_refresh()
        if silent.valid:
            return silent

        if auth_token:
            return refresh_license_token(auth_token)

        return LicenseCheckResult(
            valid=False,
            license=None,
            error="No license found. Run 'g-gremlin auth login' to authenticate.",
            warnings=[],
        )

    # Cached JWT exists but is expired (or meta missing) - try silent refresh.
    if expires_at <= now:
        silent = attempt_silent_refresh()
        if silent.valid:
            return silent

        if auth_token:
            return refresh_license_token(auth_token)

        return silent

    # Check if refresh is needed
    if _token_needs_refresh(meta):
        silent = attempt_silent_refresh()
        if silent.valid:
            return silent
        if auth_token:
            result = refresh_license_token(auth_token)
            if result.valid:
                return result
            # On refresh failure, fall through to use cached token

        # Use cached token but warn about refresh
        cached = get_cached_license()
        if cached:
            return LicenseCheckResult(
                valid=True,
                license=cached,
                error=None,
                warnings=["License token will expire soon. Run 'g-gremlin auth refresh' to update."],
            )

    # Token is fresh, use cached info
    cached = get_cached_license()
    if cached:
        warnings = []

        # Add trial warnings
        if _is_trial_tier(cached.tier) and cached.days_remaining is not None:
            if cached.days_remaining <= 1:
                warnings.append("Your trial ends tomorrow!")
            elif cached.days_remaining <= 3:
                warnings.append(f"Your trial ends in {cached.days_remaining} days.")
            elif cached.days_remaining <= 7:
                warnings.append(f"Trial: {cached.days_remaining} days remaining.")

        return LicenseCheckResult(
            valid=True,
            license=cached,
            error=None,
            warnings=warnings,
        )

    return LicenseCheckResult(
        valid=False,
        license=None,
        error="Invalid cached license. Run 'g-gremlin auth login' to re-authenticate.",
        warnings=[],
    )


def check_license_unified(auth_token: Optional[str] = None) -> LicenseCheckResult:
    """
    Unified entitlement resolver.

    Priority:
    1) Ed25519 offline license file (~/.g_gremlin/license.json)
    2) Legacy JWT token
    3) Local Free defaults
    """
    # Dev mode bypass keeps full access while still returning structured entitlements.
    if dev_mode.is_dev_mode_enabled():
        entitlements, limits = _tier_defaults("platform")
        return LicenseCheckResult(
            valid=True,
            license=LicenseInfo(
                tier="dev_unlimited",
                status="active",
                trial_ends_at=None,
                days_remaining=None,
                features=["all"],
                token_expires_at=int(time.time()) + 86400 * 365,
            ),
            error=None,
            warnings=["[DEV MODE] License checks bypassed"],
            plan="platform",
            trial=False,
            source="dev_mode",
            entitlements=entitlements,
            limits=limits,
        )

    local_entitlements, local_limits = _tier_defaults("community")

    # V2 signed license path (authoritative if present and valid).
    from g_gremlin.credentials import license_v2
    v2_fallback_warnings: list[str] = []

    if license_v2.LICENSE_PATH.exists():
        verified = license_v2.verify_license(license_v2.LICENSE_PATH)
        if not verified.valid or not verified.payload:
            warnings = list(verified.warnings)
            if verified.error:
                warnings.append(f"[license-v2] {verified.error}")
            # Invalid v2 files should not block valid JWT fallback.
            v2_fallback_warnings.extend(warnings)
        else:
            payload = verified.payload
            within_window, within_grace, window_error = license_v2.evaluate_license_window(payload)
            entitlements = license_v2.effective_entitlements(payload, local_entitlements)
            limits = license_v2.effective_limits(payload, local_limits)

            warnings = list(verified.warnings)
            if within_grace:
                warnings.append("License is in grace period.")
            if window_error and not within_grace:
                warnings.append(window_error)

            # If outside paid window and grace, degrade to Local Free without hard failure.
            degraded_to_free = (not within_window) and (not within_grace)
            effective_plan = "community" if degraded_to_free else payload.plan

            return LicenseCheckResult(
                valid=True,
                license=LicenseInfo(
                    tier=effective_plan,
                    status="active" if not degraded_to_free else "expired",
                    trial_ends_at=payload.expires_at if payload.trial else None,
                    days_remaining=None,
                    features=[name for name, enabled in entitlements.items() if enabled],
                    token_expires_at=_iso_to_unix(payload.expires_at),
                ),
                error=window_error if degraded_to_free else None,
                warnings=warnings,
                plan=effective_plan,
                trial=payload.trial,
                source="license_v2",
                license_id=payload.license_id,
                account_id=payload.account_id,
                issued_at=payload.issued_at,
                expires_at=payload.expires_at,
                grace_period_hours=payload.grace_period_hours,
                schema_version=payload.schema_version,
                issuer=payload.issuer,
                kid=payload.kid,
                entitlements_hash=payload.entitlements_hash,
                entitlements=entitlements,
                limits=limits,
            )

    # JWT fallback path.
    jwt_result = check_license(auth_token=auth_token)
    if jwt_result.valid:
        tier = jwt_result.license.tier if jwt_result.license else None
        plan = _legacy_jwt_plan(tier)
        entitlements, limits = _tier_defaults(plan)
        warnings = list(v2_fallback_warnings) + list(jwt_result.warnings)
        warnings.append("JWT license path is deprecated; run 'g-gremlin license install <path-or-token>'.")
        return LicenseCheckResult(
            valid=True,
            license=jwt_result.license,
            error=None,
            warnings=warnings,
            plan=plan,
            trial=bool(jwt_result.license and _is_trial_tier(jwt_result.license.tier)),
            source="jwt",
            entitlements=entitlements,
            limits=limits,
        )

    # No valid JWT; still operate in Local Free mode.
    return LicenseCheckResult(
        valid=True,
        license=jwt_result.license,
        error=jwt_result.error,
        warnings=list(v2_fallback_warnings) + list(jwt_result.warnings),
        plan="community",
        trial=False,
        source="license_v2_invalid" if v2_fallback_warnings else "community",
        entitlements=local_entitlements,
        limits=local_limits,
    )


def print_license_warnings(result: LicenseCheckResult) -> None:
    """Print any license warnings to stderr."""
    import click

    for warning in result.warnings:
        click.secho(f"[Gremlin] {warning}", fg="yellow", err=True)


def print_license_error(result: LicenseCheckResult) -> None:
    """Print license error and exit."""
    import click

    click.secho(f"[Gremlin] {result.error}", fg="red", err=True)

    if "trial" in (result.error or "").lower():
        click.secho(
            "[Gremlin] Subscribe at: https://foundryops.io/dashboard?upgrade=agent",
            fg="yellow",
            err=True,
        )
    elif "no license" in (result.error or "").lower():
        click.secho(
            "[Gremlin] Start a trial at: https://app.foundryops.io/agent/quickstart",
            fg="yellow",
            err=True,
        )


def report_usage(
    command: str,
    rows_processed: int = 0,
    fg_lookups: int = 0,
    docs_published: int = 0,
) -> None:
    """
    Report usage metrics to FoundryOps API (fire-and-forget).

    Called after successful command execution to track usage for
    trial value display and conversion optimization.
    """
    token = get_license_token()
    if not token:
        return  # No token, skip reporting

    try:
        # Fire-and-forget, don't block CLI
        httpx.post(
            f"{FOUNDRYOPS_API_URL}/api/v1/agent/usage",
            headers={"Authorization": f"Bearer {token}"},
            json={
                "command": command,
                "rowsProcessed": rows_processed,
                "fgLookups": fg_lookups,
                "docsPublished": docs_published,
            },
            timeout=5.0,  # Short timeout to not block CLI
        )
    except Exception:
        # Silently ignore errors - usage reporting is non-critical
        pass


# Type variable for decorator
F = TypeVar("F", bound=Callable[..., Any])


def require_license(func: F) -> F:
    """
    Decorator to require a valid license before running a command.

    Usage:
        @click.command()
        @require_license
        def my_command():
            ...
    """

    @wraps(func)
    def wrapper(*args, **kwargs):
        result = check_license()

        if not result.valid:
            print_license_error(result)
            sys.exit(1)

        # Print warnings but continue
        print_license_warnings(result)

        return func(*args, **kwargs)

    return wrapper  # type: ignore
