"""metr Django decorator — local JWT verify + async metering. Requires Django 4.1+."""

import base64
import hashlib
import json
import os
import time
from functools import wraps
from typing import Optional

import httpx
from django.http import JsonResponse

DEFAULT_GATEWAY_URL = "https://api.metr.dev"

_cached_public_key: Optional[bytes] = None


def _b64url_decode(s: str) -> bytes:
    s = s.replace("-", "+").replace("_", "/")
    padding = 4 - len(s) % 4
    if padding != 4:
        s += "=" * padding
    return base64.b64decode(s)


def _get_public_key(gateway_url: str, configured_key: Optional[str]) -> bytes:
    global _cached_public_key
    if _cached_public_key is not None:
        return _cached_public_key
    if configured_key:
        _cached_public_key = _b64url_decode(configured_key)
        return _cached_public_key
    res = httpx.get(f"{gateway_url}/.well-known/metr.json")
    _cached_public_key = _b64url_decode(res.json()["public_key"])
    return _cached_public_key


def _verify_jwt(token: str, public_key: bytes) -> dict:
    from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

    parts = token.split(".")
    if len(parts) != 3:
        raise ValueError("invalid JWT format")
    message = f"{parts[0]}.{parts[1]}".encode()
    signature = _b64url_decode(parts[2])
    key = Ed25519PublicKey.from_public_bytes(public_key)
    key.verify(signature, message)
    payload = json.loads(_b64url_decode(parts[1]))
    if int(time.time()) > payload.get("exp", 0):
        raise ValueError("token expired")
    return payload


def _extract_token_sig(token: str) -> str:
    parts = token.split(".")
    if len(parts) != 3:
        return ""
    return _b64url_decode(parts[2])[:16].hex()


def require_payment(
    price: float,
    unit_type: str = "request",
    api_key: str = None,
    endpoint_id: str = None,
    gateway_url: str = None,
    public_key: str = None,
    max_token_age: int = None,
    require_ip: bool = False,
):
    """
    Decorator that requires payment for a Django async view.
    Requires Django 4.1+ async views.

    Usage:
        @require_payment(price=0.001)
        async def summarize(request):
            return JsonResponse({"summary": "..."})
    """
    _api_key = api_key or os.environ.get("METR_API_KEY")
    _endpoint_id = endpoint_id or os.environ.get("METR_ENDPOINT_ID")
    _gateway_url = gateway_url or os.environ.get("METR_GATEWAY_URL", DEFAULT_GATEWAY_URL)
    _public_key = public_key or os.environ.get("METR_PUBLIC_KEY")

    if not _api_key:
        raise ValueError("metr: API key required.")

    def decorator(view_func):
        @wraps(view_func)
        async def wrapper(request, *args, **kwargs):
            token = request.headers.get("X-Metr-Token")

            if not token:
                return JsonResponse(
                    {"error": "payment_required", "message": "Deposit funds at metr.dev.",
                     "deposit_url": f"{_gateway_url}/api/v1/wallet/deposit",
                     "price": price, "unit_type": unit_type},
                    status=402,
                )

            try:
                pk = _get_public_key(_gateway_url, _public_key)
                claims = _verify_jwt(token, pk)
            except ValueError as e:
                return JsonResponse(
                    {"error": "invalid_token", "message": str(e),
                     "refresh_url": f"{_gateway_url}/api/v1/token/refresh"},
                    status=402,
                )
            except Exception:
                return JsonResponse({"error": "invalid_token"}, status=402)

            if max_token_age is not None:
                if int(time.time()) - claims.get("iat", 0) > max_token_age:
                    return JsonResponse(
                        {"error": "token_too_old", "refresh_url": f"{_gateway_url}/api/v1/token/refresh"},
                        status=402,
                    )

            if require_ip:
                client_ip = request.META.get("REMOTE_ADDR", "")
                ips = claims.get("ips", [])
                if not ips or client_ip not in ips:
                    return JsonResponse({"error": "ip_not_allowed"}, status=403)

            request.metr = {"buyer_id": claims["sub"], "token_issued_at": claims.get("iat", 0)}

            result = await view_func(request, *args, **kwargs)

            # Fire-and-forget metering
            token_sig = _extract_token_sig(token)
            minute = time.strftime("%Y%m%d%H%M")
            idemp = hashlib.sha256(f"{token_sig}{_endpoint_id}{minute}".encode()).hexdigest()

            try:
                async with httpx.AsyncClient() as client:
                    await client.post(
                        f"{_gateway_url}/api/v1/meter",
                        headers={"X-Metr-Api-Key": _api_key},
                        json={
                            "endpoint_id": _endpoint_id,
                            "buyer_token_sig": token_sig,
                            "buyer_id": claims["sub"],
                            "units": 1, "unit_type": unit_type,
                            "idempotency_key": idemp,
                        },
                    )
            except Exception:
                pass

            return result

        return wrapper
    return decorator
