from __future__ import annotations

import json
import logging
from typing import Any, Callable

logger = logging.getLogger(__name__)

# Maximum accepted license-key payload size (defense against memory exhaustion
# from an oversized request body). Mirrors lib.licensing._MAX_LICENSE_FILE_SIZE.
_MAX_LICENSE_KEY_BYTES = 1 * 1024 * 1024  # 1 MiB


def make_api_license_router() -> Callable[[str, dict[str, Any] | None], Callable[[], Any]]:
    from lib.licensing import get_license_manager
    lm = get_license_manager()

    def handle(request_path: str, request_body: dict[str, Any] | None) -> Callable[[], Any]:
        if not request_path:
            raise ValueError("request_path must not be empty")

        normalized = request_path.rstrip("/")

        if normalized == "/api/license/status":
            return _handle_status(lm)
        elif normalized == "/api/license/validate":
            return _handle_validate(lm, request_body)
        elif normalized == "/api/license/activate":
            return _handle_activate(lm, request_body)
        elif normalized == "/api/license/deactivate":
            return _handle_deactivate(lm, request_body)
        else:
            raise ValueError(f"Unknown license route: {request_path!r}")

    return handle


def _parse_license_key(license_key: str) -> dict[str, Any]:
    """Parse a JSON license-key string into a license dict.

    The license key issued by the Donjon license admin tool is a signed
    JSON document. Accepts the raw JSON object. Raises ValueError on any
    malformed input so the API surface returns 400, never a 500.
    """
    if len(license_key.encode("utf-8")) > _MAX_LICENSE_KEY_BYTES:
        raise ValueError("license_key payload too large")
    try:
        data = json.loads(license_key)
    except (json.JSONDecodeError, ValueError) as exc:
        raise ValueError(f"license_key is not valid JSON: {exc}") from exc
    if not isinstance(data, dict):
        raise ValueError("license_key must decode to a JSON object")
    return data


def _handle_status(lm: Any) -> Callable[[], dict[str, Any]]:
    def json_response() -> dict[str, Any]:
        # Use the real LicenseManager API (get_tier / get_tier_label /
        # get_limits / get_license_info). The previous implementation called
        # a non-existent guard.get_status() which raised AttributeError (500).
        return {
            "ok": True,
            "status": {
                "tier": lm.get_tier(),
                "label": lm.get_tier_label(),
                "limits": lm.get_limits(),
                "license": lm.get_license_info(),
            },
        }

    return json_response


def _handle_validate(
    lm: Any,
    body: dict[str, Any] | None,
) -> Callable[[], dict[str, Any]]:
    def json_response() -> dict[str, Any]:
        if body is None:
            raise ValueError("Request body required for /api/license/validate")
        license_key = body.get("license_key")
        if not license_key or not isinstance(license_key, str):
            raise ValueError("Field 'license_key' (str) is required")
        license_data = _parse_license_key(license_key)
        # validate_license() runs the full chain (signature, expiry,
        # fingerprint, revocation, tier validity) and returns a bool.
        valid = lm.validate_license(license_data)
        detail = "License is valid" if valid else "License is invalid or expired"
        return {"ok": True, "valid": bool(valid), "detail": detail}

    return json_response


def _handle_activate(
    lm: Any,
    body: dict[str, Any] | None,
) -> Callable[[], dict[str, Any]]:
    def json_response() -> dict[str, Any]:
        if body is None:
            raise ValueError("Request body required for /api/license/activate")
        license_key = body.get("license_key")
        if not license_key or not isinstance(license_key, str):
            raise ValueError("Field 'license_key' (str) is required")
        license_data = _parse_license_key(license_key)

        # Validate before persisting; refuse to write an invalid license.
        if not lm.validate_license(license_data):
            return {
                "ok": True,
                "activated": False,
                "detail": "License is invalid or expired; not activated",
            }

        # Persist the license to the canonical location and reload so the
        # new tier takes effect without a restart.
        try:
            lm.license_path.parent.mkdir(parents=True, exist_ok=True)
            lm.license_path.write_text(
                json.dumps(license_data), encoding="utf-8"
            )
            lm.reload()
        except OSError as exc:
            logger.warning("Failed to persist license file: %s", exc)
            raise ValueError(f"Could not write license file: {exc}") from exc

        activated = lm.get_tier() != "community"
        detail = (
            f"License activated (tier={lm.get_tier()})"
            if activated
            else "License written but did not elevate tier"
        )
        return {"ok": True, "activated": bool(activated), "detail": detail}

    return json_response


def _handle_deactivate(
    lm: Any,
    body: dict[str, Any] | None,
) -> Callable[[], dict[str, Any]]:
    def json_response() -> dict[str, Any]:
        # Deactivation removes the installed license file (if any) and
        # reloads, dropping the manager back to the community tier. A body
        # is optional here; we accept and ignore an optional license_key.
        removed = False
        try:
            if lm.license_path.exists():
                lm.license_path.unlink()
                removed = True
            lm.reload()
        except OSError as exc:
            logger.warning("Failed to remove license file: %s", exc)
            raise ValueError(f"Could not remove license file: {exc}") from exc

        detail = (
            "License deactivated; reverted to community tier"
            if removed
            else "No active license file to deactivate; tier is community"
        )
        return {"ok": True, "deactivated": bool(removed), "detail": detail}

    return json_response


def handle_license_request(
    request_path: str,
    request_body: dict[str, Any] | None,
) -> Callable[[], Any]:
    router = make_api_license_router()
    return router(request_path, request_body)
