"""License enforcement — validates licenses, middleware, and feature gates."""
from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import Callable, Optional

from fastapi import Depends, HTTPException, Request, Response
from starlette.middleware.base import BaseHTTPMiddleware

from .client import LicenseClient
from .keygen import decode_license_file
from .models import LicenseInfo, LicenseTier, TIER_DEFAULTS

logger = logging.getLogger(__name__)


class LicenseEnforcer:
    """Validates and enforces license constraints at runtime."""

    def __init__(self, signing_key: str = ""):
        self._signing_key = signing_key
        self._license: Optional[LicenseInfo] = None

    @property
    def license(self) -> Optional[LicenseInfo]:
        return self._license

    @property
    def tier(self) -> LicenseTier:
        if self._license:
            return self._license.tier
        return LicenseTier.COMMUNITY

    def load_from_file(self, encoded: str) -> bool:
        """Load and validate an encoded license file."""
        if not self._signing_key:
            logger.warning("No signing key configured — using community defaults")
            self._license = LicenseInfo()
            return True
        info = decode_license_file(encoded, self._signing_key)
        if info is None:
            logger.error("License signature verification failed")
            return False
        if not self._check_expiry(info):
            return False
        self._license = info
        logger.info("License loaded: tier=%s customer=%s", info.tier.value, info.customer_name)
        return True

    def load_from_info(self, info: LicenseInfo) -> bool:
        """Load a pre-validated LicenseInfo directly."""
        if not self._check_expiry(info):
            return False
        self._license = info
        return True

    def check_feature(self, feature: str) -> bool:
        """Check whether a feature is allowed under the current license."""
        if self._license is None:
            defaults = TIER_DEFAULTS[LicenseTier.COMMUNITY]
            return feature in defaults["features"]
        return feature in self._license.features

    def check_node_limit(self, current_nodes: int) -> bool:
        """Check whether adding another node is allowed."""
        if self._license is None:
            max_nodes = TIER_DEFAULTS[LicenseTier.COMMUNITY]["max_nodes"]
        else:
            max_nodes = self._license.max_nodes
        if max_nodes == 0:  # unlimited
            return True
        return current_nodes < max_nodes

    def is_valid(self) -> bool:
        """Check whether the current license is valid and not expired."""
        if self._license is None:
            return True  # community mode is always valid
        return self._license.valid and self._check_expiry(self._license)

    def _check_expiry(self, info: LicenseInfo) -> bool:
        """Check if a license has expired."""
        if info.expires_at is None:
            return True
        now = datetime.now(timezone.utc)
        expires = info.expires_at
        if expires.tzinfo is None:
            expires = expires.replace(tzinfo=timezone.utc)
        if now > expires:
            logger.warning("License expired at %s", expires.isoformat())
            info.valid = False
            return False
        return True


# ── FastAPI Middleware ────────────────────────────────────────────────────────


class LicenseMiddleware(BaseHTTPMiddleware):
    """Checks license validity on every request and injects tier header.

    Graceful degradation on expiry:
    - Health, heartbeat, registration, and dashboard endpoints ALWAYS work
    - Expired paid licenses fall back to community tier (not a hard block)
    - A warning header is added when license is expired or nearing expiry
    """

    # Paths that always work regardless of license status
    ALWAYS_ALLOWED = (
        "/health", "/docs", "/openapi.json",
        "/nodes/register", "/nodes/", "/dashboard",
        "/licensing",
    )

    def __init__(self, app, license_client: LicenseClient):
        super().__init__(app)
        self.license_client = license_client

    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        info = self.license_client.info
        path = request.url.path.rstrip("/")

        # Always allow critical infrastructure endpoints
        is_critical = any(path.startswith(p) or path == p.rstrip("/")
                         for p in self.ALWAYS_ALLOWED)

        if not info.valid and not is_critical:
            # Graceful degradation: fall back to community tier instead of blocking
            community = TIER_DEFAULTS[LicenseTier.COMMUNITY]
            request.state.license_tier = LicenseTier.COMMUNITY.value
            request.state.license_features = community["features"]
            request.state.license_max_nodes = community["max_nodes"]
            request.state.license_expired = True

            response = await call_next(request)
            response.headers["X-License-Tier"] = LicenseTier.COMMUNITY.value
            response.headers["X-License-Warning"] = "License expired — running in community mode"
            return response

        # Normal valid license path
        request.state.license_tier = info.tier.value
        request.state.license_features = info.features
        request.state.license_max_nodes = info.max_nodes
        request.state.license_expired = False

        # Check for upcoming expiry (warn within 14 days)
        warning = None
        if info.expires_at:
            now = datetime.now(timezone.utc)
            expires = info.expires_at
            if expires.tzinfo is None:
                expires = expires.replace(tzinfo=timezone.utc)
            days_left = (expires - now).days
            if days_left <= 14:
                warning = f"License expires in {days_left} day(s)"
        request.state.license_warning = warning

        response = await call_next(request)
        response.headers["X-License-Tier"] = info.tier.value
        if warning:
            response.headers["X-License-Warning"] = warning
        return response


# ── Node Limit Hook ──────────────────────────────────────────────────────────


def enforce_node_limit(coordinator, license_client: LicenseClient) -> bool:
    """Check if a new node registration should be allowed.

    Returns True if allowed, False if at or over the node limit.
    """
    current = len(coordinator.get_online_nodes())
    if not license_client.check_node_limit(current + 1):
        logger.warning(
            "Node limit reached: %d/%d (tier=%s)",
            current,
            license_client.info.max_nodes,
            license_client.info.tier.value,
        )
        return False
    return True


# ── Feature Gate Dependencies ────────────────────────────────────────────────


def enforce_feature(feature_name: str):
    """FastAPI dependency that checks feature access.

    Usage:
        @app.post("/endpoint", dependencies=[Depends(enforce_feature("job_routing"))])
    """

    def _checker(request: Request):
        features = getattr(request.state, "license_features", [])
        if feature_name not in features:
            tier = getattr(request.state, "license_tier", "community")
            raise HTTPException(
                status_code=403,
                detail=f"Feature '{feature_name}' requires a higher license tier (current: {tier})",
            )

    return Depends(_checker)


# ── Feature Gate Constants ───────────────────────────────────────────────────
# Community (free): dashboard, api_access, job_routing, amd_gpu_optimization, one_time_probe
# Professional: + continuous_optimization, auto_tuning, jepa_training, nvidia/cpu/fpga/memory, codex
# Enterprise: + multi_gpu, credential_vault, priority_support

# These are FREE — never gate them
# REQUIRE_DASHBOARD = enforce_feature("dashboard")        # always free
# REQUIRE_JOB_ROUTING = enforce_feature("job_routing")    # always free
# REQUIRE_AMD_GPU = enforce_feature("amd_gpu_optimization")  # always free

# These require Professional or higher
REQUIRE_CONTINUOUS_OPTIMIZATION = enforce_feature("continuous_optimization")
REQUIRE_AUTO_TUNING = enforce_feature("auto_tuning")
REQUIRE_JEPA_TRAINING = enforce_feature("jepa_training")
REQUIRE_NVIDIA_OPTIMIZATION = enforce_feature("nvidia_optimization")
REQUIRE_CPU_OPTIMIZATION = enforce_feature("cpu_optimization")
# Codex agents are internal dev tooling, not customer-facing — no license gate needed
# REQUIRE_CODEX_AGENTS = enforce_feature("codex_agents")

# These require Enterprise
REQUIRE_MULTI_GPU = enforce_feature("multi_gpu")

# Credential vault is FREE for all tiers — security should never be paywalled
# REQUIRE_CREDENTIAL_VAULT = enforce_feature("credential_vault")  # always available
