from __future__ import annotations

import os
import time
from typing import Iterable, Optional

import jwt


class AuthError(Exception):
    pass


JWT_SECRET = os.getenv("VOICE_JWT_SECRET") or os.getenv("JWT_SECRET", "dev-secret-change-me")
JWT_ISSUER = os.getenv("JWT_ISSUER", "sales-voice")
JWT_AUDIENCE = os.getenv("JWT_AUDIENCE", "sales-voice")
JWT_LEEWAY_SECONDS = int(os.getenv("JWT_LEEWAY_SECONDS", "30"))
ENVIRONMENT = os.getenv("ENV", os.getenv("ENVIRONMENT", "development")).lower()

ACCESS_TTL_SECONDS = int(os.getenv("VOICE_ACCESS_TTL_SECONDS", "900"))
VOICE_TTL_SECONDS = int(os.getenv("VOICE_SESSION_TTL_SECONDS", "120"))

# Fail fast if default secret is used in non-dev environments
if ENVIRONMENT in {"prod", "production"} and JWT_SECRET == "dev-secret-change-me":
    raise RuntimeError("JWT secret must be set in production")


def _now() -> int:
    return int(time.time())


def _encode(payload: dict) -> str:
    return jwt.encode(payload, JWT_SECRET, algorithm="HS256")


def issue_access_token(
    subject: str,
    org: Optional[str],
    plan: str,
    scope: Iterable[str],
) -> tuple[str, int]:
    now = _now()
    payload = {
        "sub": subject,
        "org": org,
        "plan": plan,
        "scope": list(scope),
        "type": "access",
        "iat": now,
        "exp": now + ACCESS_TTL_SECONDS,
        "iss": JWT_ISSUER,
        "aud": JWT_AUDIENCE,
    }
    return _encode(payload), ACCESS_TTL_SECONDS


def issue_voice_token(
    subject: str,
    org: Optional[str],
    plan: str,
    scope: Iterable[str],
    sid: str,
) -> tuple[str, int]:
    now = _now()
    payload = {
        "sub": subject,
        "org": org,
        "plan": plan,
        "scope": list(scope),
        "sid": sid,
        "type": "voice",
        "iat": now,
        "exp": now + VOICE_TTL_SECONDS,
        "iss": JWT_ISSUER,
        "aud": JWT_AUDIENCE,
    }
    return _encode(payload), VOICE_TTL_SECONDS


def verify_token(token: str, required_scope: Optional[str] = None) -> dict:
    try:
        payload = jwt.decode(
            token,
            JWT_SECRET,
            algorithms=["HS256"],
            audience=JWT_AUDIENCE,
            issuer=JWT_ISSUER,
            leeway=JWT_LEEWAY_SECONDS,
            options={
                "require": ["sub", "exp", "iat", "iss", "aud", "type"],
            },
        )
    except jwt.PyJWTError as exc:
        raise AuthError("Invalid or expired token") from exc

    token_type = payload.get("type")
    if token_type not in {"access", "voice"}:
        raise AuthError("Invalid token type")

    scopes = set(payload.get("scope") or [])
    if required_scope and required_scope not in scopes:
        raise AuthError("Missing required scope")
    return payload
