from __future__ import annotations

from datetime import datetime, timedelta, timezone

import jwt

from app.core.common.identifiers import new_governance_uid


class TokenError(ValueError):
    pass


def issue_access_token(
    *,
    user_id: str,
    roles: list[str],
    secret: str,
    now: datetime | None = None,
    lifetime: timedelta = timedelta(minutes=30),
) -> str:
    now = now or datetime.now(timezone.utc)
    claims = {
        "sub": str(user_id),
        "roles": sorted(set(roles)),
        "iat": int(now.timestamp()),
        "exp": int((now + lifetime).timestamp()),
        "jti": new_governance_uid(),
    }
    return jwt.encode(claims, secret, algorithm="HS256")


def decode_access_token(
    token: str,
    *,
    secret: str,
    now: datetime | None = None,
) -> dict:
    try:
        claims = jwt.decode(
            token,
            secret,
            algorithms=["HS256"],
            options={"verify_exp": False, "require": ["sub", "roles", "iat", "exp", "jti"]},
        )
    except jwt.PyJWTError as exc:
        raise TokenError("invalid access token") from exc

    current = int((now or datetime.now(timezone.utc)).timestamp())
    if int(claims["exp"]) <= current:
        raise TokenError("access token expired")
    if int(claims["iat"]) > current + 30:
        raise TokenError("access token issued in the future")
    if not isinstance(claims.get("roles"), list):
        raise TokenError("invalid role claims")
    return {key: claims[key] for key in ("sub", "roles", "iat", "exp", "jti")}
