import datetime
import logging
from base64 import b64decode
from typing import Any
from urllib.parse import quote, urlencode

import jwt
import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.types import rsa
from django.contrib.auth.models import User

from yarik_django_auth.conf.settings import (
    CLIENT_ID,
    CLIENT_SECRET,
    DEFAULT_REQUEST_TIMEOUT,
    KEYCLOAK_AUDIENCE,
    KEYCLOAK_CERT_FILENAME,
    KEYCLOAK_IS_CREATE,
    KEYCLOAK_REDIRECT_URI,
    KEYCLOAK_SCOPES,
    KEYCLOAK_URL_BASE,
    REALM_NAME,
)

logger = logging.getLogger(__name__)


class KeycloakConfidentialBackend:
    @staticmethod
    def exchange_code_for_token(code: str) -> dict[str, Any] | None:
        """Возвращает токен пользователя."""
        token_endpoint = f"{KEYCLOAK_URL_BASE}realms/{REALM_NAME}/protocol/openid-connect/token"

        payload: dict[str, Any] = {
            "code": code,
            "grant_type": "authorization_code",
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "redirect_uri": KEYCLOAK_REDIRECT_URI,
            "scope": " ".join(KEYCLOAK_SCOPES or []),
        }

        response = requests.post(
            token_endpoint,
            data=urlencode(payload, quote_via=quote),
            headers={"Content-Type": "application/x-www-form-urlencoded"},
            verify=KEYCLOAK_CERT_FILENAME,
            timeout=DEFAULT_REQUEST_TIMEOUT,
        )

        if response.status_code == 200:
            r = response.json()
            logger.debug("Ответ токеном: %s", r)
            return r
        logger.info("Токен не получен: %s", response)
        return None

    @property
    def public_key(self) -> rsa.RSAPublicKey:
        """Возвращает публичный ключ из Keycloak."""
        r = requests.get(
            f"{KEYCLOAK_URL_BASE}realms/{REALM_NAME}/",
            verify=KEYCLOAK_CERT_FILENAME,
            timeout=DEFAULT_REQUEST_TIMEOUT,
        )
        r.raise_for_status()
        key_der_base64 = r.json()["public_key"]
        key_der = b64decode(key_der_base64.encode())
        pk = serialization.load_der_public_key(key_der)
        if not isinstance(pk, rsa.RSAPublicKey):
            msg = "Тип публичного ключа не поддерживается"
            raise TypeError(msg)

        return pk

    def decode_token(self, data: dict[str, Any]) -> dict[str, Any]:
        """Возвращает декодированные данные из токена."""
        access_token = data.get("access_token")
        if not access_token:
            msg = "access_token не найден в словаре"
            raise KeyError(msg)

        access_token = str(access_token)

        logger.debug("Текущее время: %s", datetime.datetime.now(tz=datetime.UTC))
        logger.debug("Публичный ключ: %s", self.public_key.public_numbers())

        return jwt.decode(
            access_token,
            key=self.public_key,
            algorithms=["RS256"],
            audience=KEYCLOAK_AUDIENCE,
        )

    def authenticate(self, token: dict[str, Any]) -> User | None:
        logger.debug("Токен: %s", token)
        logger.info("Декодирование токена доступа...")
        user_info = self.decode_token(token)
        logger.debug("Декодированный токен: %s", user_info)
        logger.info("Токен успешно декодирован!")
        logger.info("Проверка роли...")
        if (
            CLIENT_ID not in user_info["resource_access"]
            or "access" not in user_info["resource_access"][CLIENT_ID]["roles"]
        ):
            msg = "Не назначена роль access!"  # noqa: RUF001
            logger.debug(msg)
            raise PermissionError(msg)
        logger.info("Роль успешно проверена, доступ получен!")

        logger.info("Поиск пользователя в БД")
        user = self.get_user(user_info=user_info)

        if not user:
            logger.info("Пользователь не найден")

        return user

    def get_user(self, user_info: dict[str, Any]) -> User | None:
        logger.debug("Метаданные пользователя Keycloak: %s", user_info)
        user = User.objects.filter(username=user_info.get("preferred_username", "")).first()
        user_roles = user_info.get("resource_access", {}).get(CLIENT_ID, {}).get("roles", [])
        if KEYCLOAK_IS_CREATE:
            username = user_info.get("preferred_username")
            if not username:
                msg = "Не задано имя пользователя в токене"  # noqa: RUF001
                raise ValueError(msg)
            if not user:
                # Создание пользователя
                logger.info("Данный пользователь не зарегистрирован в локальной БД: создание пользователя")
                user = User.objects.create_user(
                    username=username,
                    email=user_info.get("email"),
                    last_name=user_info.get("given_name"),
                    first_name=user_info.get("family_name"),
                    is_superuser="superuser" in user_roles,
                    is_staff="staff" in user_roles,
                )

                # Установка пустого пароля
                user.set_unusable_password()

                # Сохранение пользователя
                user.save()
            else:
                logger.info("Данный пользователь уже зарегистрирован в локальной БД: обновление информации")
                user.email = user_info.get("email") or ""
                user.last_name = user_info.get("given_name") or ""
                user.first_name = user_info.get("family_name") or ""
                user.is_superuser = "superuser" in user_roles
                user.is_staff = "staff" in user_roles
                user.save()
        return user
