"""HMAC-signed proxy tokens.

The streaming proxy must hand its segment URLs to native HLS players
(Android ExoPlayer, AVPlayer, hls.js, VLC) which CANNOT add custom HTTP
headers to segment fetches. Standard solution: bake a short-lived,
HMAC-signed token directly into the URL path so the server can validate
each segment request stateless-ly.

Token format::

    base64url(payload_json) + "." + base64url(hmac_sha256(payload_json))

Payload schema::

    {
      "v":   1,                  # version, lets us evolve the schema
      "k":   "live" | "vod",     # kind
      "pid": "shared:abc123",    # shared profile id (NEVER user-owned)
      "sid": "12345",            # stream_id
      "st":  "movie" | "series", # vod sub-type (vod kind only)
      "ext": "mp4",              # file extension (vod kind only)
      "ub":  "http://server.tld/live/u/p",  # upstream base URL
                                            # (live kind only — used to
                                            # validate segment URLs)
      "exp": 1735689600          # unix expiry (UTC)
    }

The HMAC is keyed with the same JWT_SECRET we already use for auth
tokens — guarantees a stolen proxy token can't be forged without
also leaking the master secret.
"""
from __future__ import annotations

import base64
import hashlib
import hmac
import json
import os
import time
from typing import Any, Dict, Optional


def _secret() -> bytes:
    s = os.environ.get("JWT_SECRET")
    if not s:
        # Fail loud — refusing to sign with a guessable default is the
        # right call for a security-sensitive helper.
        raise RuntimeError("JWT_SECRET not configured")
    return s.encode("utf-8")


def _b64u_encode(b: bytes) -> str:
    return base64.urlsafe_b64encode(b).rstrip(b"=").decode("ascii")


def _b64u_decode(s: str) -> bytes:
    pad = "=" * (-len(s) % 4)
    return base64.urlsafe_b64decode((s + pad).encode("ascii"))


# Default token TTL — 30 min. Confirmed by user on May 2026 brief.
DEFAULT_TTL_SECONDS = 30 * 60


def sign_token(payload: Dict[str, Any], ttl_seconds: int = DEFAULT_TTL_SECONDS) -> str:
    """Sign a payload and return the URL-safe token string."""
    body = dict(payload)
    body["exp"] = int(time.time()) + int(ttl_seconds)
    raw = json.dumps(body, separators=(",", ":"), sort_keys=True).encode("utf-8")
    sig = hmac.new(_secret(), raw, hashlib.sha256).digest()
    return f"{_b64u_encode(raw)}.{_b64u_encode(sig)}"


def verify_token(token: str) -> Optional[Dict[str, Any]]:
    """Return the payload if the token is valid + unexpired, else None.

    Reasons it returns None (do NOT log the precise reason to the caller —
    just 403):
      • malformed shape (not "payload.signature")
      • signature mismatch
      • expired
      • base64 / JSON decoding error
    """
    if not token or "." not in token:
        return None
    try:
        body_b64, sig_b64 = token.rsplit(".", 1)
        raw = _b64u_decode(body_b64)
        sig = _b64u_decode(sig_b64)
    except Exception:
        return None
    expected = hmac.new(_secret(), raw, hashlib.sha256).digest()
    if not hmac.compare_digest(sig, expected):
        return None
    try:
        payload = json.loads(raw.decode("utf-8"))
    except Exception:
        return None
    if not isinstance(payload, dict):
        return None
    exp = payload.get("exp")
    if not isinstance(exp, int) or exp < int(time.time()):
        return None
    return payload
