"""
MWT (Mizan Web Token) — Protocol-owned identity layer.

MWT is a standard JWT (RFC 7519, HMAC-SHA256) with Mizan-specific claims,
traveling on the `X-Mizan-Token` header. It provides:

- `sub`: user_id for HMAC cache key derivation
- `pkey`: permission state hash for staleness detection
- `kid`: key ID in the JOSE header (per RFC 7515) for secret rotation
- `aud`: audience binding to prevent cross-tenant replay
- `nbf`: not-before to handle clock skew

MWT is issued from an authenticated Django session. The app handles
authentication (session, social auth, etc.); Mizan issues MWT from
the authenticated identity. Edge Workers and the origin-side cache
validate MWT to extract user identity for cache operations.

Usage:
    from mizan.mwt import create_mwt, decode_mwt, MWTUser

Configuration:
    MIZAN_MWT_SECRET: MWT signing key (separate from MIZAN_CACHE_SECRET)
    MIZAN_MWT_TTL: token lifetime in seconds (default: 300)
"""

from __future__ import annotations

import hashlib
import logging
import time
from dataclasses import dataclass
from typing import Any

import jwt

logger = logging.getLogger("mizan.mwt")


@dataclass
class MWTPayload:
    """Decoded MWT claims."""
    sub: str        # user_id
    staff: bool     # is_staff
    super: bool     # is_superuser
    pkey: str       # permission state hash (full SHA-256 hex)
    kid: str        # key ID (from JOSE header)
    aud: str        # audience
    iat: int        # issued at
    exp: int        # expiration


class MWTUser:
    """
    Minimal user object created from MWT claims.

    Used as request.user for MWT-authenticated requests.
    No database query required — all data comes from the token.
    """

    def __init__(self, payload: MWTPayload):
        self.id = int(payload.sub)
        self.pk = self.id
        self.is_staff = payload.staff
        self.is_superuser = payload.super
        self.is_authenticated = True
        self.is_anonymous = False
        self.is_active = True
        self.pkey = payload.pkey

    def __str__(self) -> str:
        return f"MWTUser(id={self.id})"

    def __repr__(self) -> str:
        return f"MWTUser(id={self.id}, pkey={self.pkey[:8]}...)"


def compute_permission_key(user: Any) -> str:
    """
    Compute a deterministic hash of the user's permission state.

    Includes is_staff, is_superuser, and all Django permissions.
    When the MWT expires and is refreshed, the new pkey reflects
    any permission changes. The short TTL controls the staleness window.

    Returns the full 64-character SHA-256 hex digest.
    """
    perms = sorted(user.get_all_permissions()) if hasattr(user, "get_all_permissions") else []
    staff = "1" if getattr(user, "is_staff", False) else "0"
    superuser = "1" if getattr(user, "is_superuser", False) else "0"
    blob = f"{staff}:{superuser}:{','.join(perms)}"
    return hashlib.sha256(blob.encode("utf-8")).hexdigest()


def create_mwt(
    user: Any,
    secret: str,
    ttl: int = 300,
    audience: str = "mizan",
    kid: str = "v1",
) -> str:
    """
    Create an MWT from an authenticated Django user.

    Args:
        user: Django user object (must have pk, is_staff, is_superuser).
        secret: MIZAN_MWT_SECRET signing key.
        ttl: Token lifetime in seconds (default: 300 = 5 minutes).
        audience: Audience claim for cross-tenant protection.
        kid: Key ID placed in JOSE header (per RFC 7515) for rotation.

    Returns:
        Encoded JWT string.
    """
    now = int(time.time())
    payload = {
        "sub": str(user.pk),
        "staff": getattr(user, "is_staff", False),
        "super": getattr(user, "is_superuser", False),
        "pkey": compute_permission_key(user),
        "aud": audience,
        "iat": now,
        "nbf": now,
        "exp": now + ttl,
    }
    # kid goes in the JOSE header per RFC 7515, not the payload
    headers = {"kid": kid}
    return jwt.encode(payload, secret, algorithm="HS256", headers=headers)


def decode_mwt(
    token: str,
    secret: str,
    audience: str = "mizan",
) -> MWTPayload | None:
    """
    Decode and validate an MWT.

    Returns MWTPayload on success, None on any failure (expired, invalid
    signature, wrong audience, not-yet-valid, malformed).
    """
    try:
        # Decode header first to extract kid
        unverified_header = jwt.get_unverified_header(token)
        kid = unverified_header.get("kid", "v1")

        data = jwt.decode(
            token,
            secret,
            algorithms=["HS256"],
            audience=audience,
        )
    except jwt.PyJWTError:
        logger.debug("MWT decode failed", exc_info=True)
        return None

    try:
        return MWTPayload(
            sub=data["sub"],
            staff=data.get("staff", False),
            super=data.get("super", False),
            pkey=data.get("pkey", ""),
            kid=kid,
            aud=audience,
            iat=data["iat"],
            exp=data["exp"],
        )
    except (KeyError, TypeError):
        logger.debug("MWT payload missing required claims", exc_info=True)
        return None
