import base64
import hashlib
import hmac
import json
import time

from django.conf import settings


class JwtAuthError(Exception):
    pass


def _base64url_encode(value):
    return base64.urlsafe_b64encode(value).rstrip(b"=").decode("ascii")


def _base64url_decode(value):
    padding = "=" * (-len(value) % 4)
    return base64.urlsafe_b64decode(f"{value}{padding}")


def _json_encode(data):
    return json.dumps(data, separators=(",", ":"), sort_keys=True).encode("utf-8")


def _sign(signing_input):
    secret = settings.SECRET_KEY.encode("utf-8")
    signature = hmac.new(secret, signing_input.encode("ascii"), hashlib.sha256).digest()
    return _base64url_encode(signature)


def create_access_token(user):
    now = int(time.time())
    expires_at = now + settings.JWT_ACCESS_TOKEN_SECONDS
    header = {"alg": "HS256", "typ": "JWT"}
    payload = {
        "type": "access",
        "sub": str(user.pk),
        "email": user.email,
        "username": user.get_username(),
        "is_staff": user.is_staff,
        "iat": now,
        "exp": expires_at,
    }

    encoded_header = _base64url_encode(_json_encode(header))
    encoded_payload = _base64url_encode(_json_encode(payload))
    signing_input = f"{encoded_header}.{encoded_payload}"

    return f"{signing_input}.{_sign(signing_input)}"


def decode_access_token(token):
    try:
        encoded_header, encoded_payload, signature = token.split(".")
    except ValueError as error:
        raise JwtAuthError("Token JWT mal forme.") from error

    signing_input = f"{encoded_header}.{encoded_payload}"
    expected_signature = _sign(signing_input)

    if not hmac.compare_digest(signature, expected_signature):
        raise JwtAuthError("Signature JWT invalide.")

    try:
        header = json.loads(_base64url_decode(encoded_header))
        payload = json.loads(_base64url_decode(encoded_payload))
    except (ValueError, json.JSONDecodeError) as error:
        raise JwtAuthError("Payload JWT invalide.") from error

    if header.get("alg") != "HS256":
        raise JwtAuthError("Algorithme JWT non supporte.")

    if payload.get("type") != "access":
        raise JwtAuthError("Type de token JWT invalide.")

    if int(payload.get("exp", 0)) < int(time.time()):
        raise JwtAuthError("Token JWT expire.")

    return payload
