from datetime import UTC, datetime, timedelta
from typing import Any
from uuid import uuid4

import jwt

from app.config import Settings


class TokenError(ValueError):
    pass


def create_token(subject: str, settings: Settings, expires_delta: timedelta, token_type: str) -> str:
    now = datetime.now(UTC)
    payload: dict[str, Any] = {
        "sub": subject,
        "type": token_type,
        "iat": now,
        "exp": now + expires_delta,
        "jti": str(uuid4()),
    }
    return jwt.encode(payload, settings.jwt_secret, algorithm=settings.jwt_algorithm)


def create_access_token(subject: str, settings: Settings) -> str:
    return create_token(subject, settings, timedelta(minutes=settings.jwt_expiration_minutes), "access")


def create_refresh_token(subject: str, settings: Settings) -> str:
    return create_token(subject, settings, timedelta(days=settings.refresh_token_expiration_days), "refresh")


def decode_token(token: str, settings: Settings, expected_type: str) -> dict[str, Any]:
    try:
        payload = jwt.decode(token, settings.jwt_secret, algorithms=[settings.jwt_algorithm])
    except jwt.PyJWTError as exc:
        raise TokenError("Invalid token") from exc
    if payload.get("type") != expected_type:
        raise TokenError("Invalid token type")
    if not isinstance(payload.get("sub"), str):
        raise TokenError("Invalid token subject")
    return payload
