import logging

import jwt
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import (
    HttpRequest,
    HttpResponse,
    HttpResponsePermanentRedirect,
    HttpResponseRedirect,
    JsonResponse,
)
from django.middleware.csrf import get_token
from django.shortcuts import redirect, render
from django.urls import reverse_lazy

from yarik_django_auth.conf.settings import (
    AUTH_PAGE_DOCUMENT,
    CACHE_CALLBACK,
    CLEAR_CACHE_CALLBACK,
    CLIENT_ID,
    DEFAULT_REDIRECT_URI,
    ERROR_PAGE_DOCUMENT,
    KEYCLOAK_POST_LOGOUT_URL,
    KEYCLOAK_REDIRECT_URI,
    KEYCLOAK_SCOPES,
    KEYCLOAK_URL_BASE_CLIENT,
    REALM_NAME,
    USE_KEYCLOAK,
    VERSION_TAG,
)
from yarik_django_auth.utils import ajax_login_required, clear_previos_sessions

from .backends import KeycloakConfidentialBackend
from .forms import LoginForm

logger = logging.getLogger(__name__)


def index(_: HttpRequest) -> HttpResponseRedirect | HttpResponsePermanentRedirect:
    return redirect(reverse_lazy("yarik_django_auth:login"))


def login_view(
    request: HttpRequest,
) -> HttpResponseRedirect | HttpResponsePermanentRedirect | HttpResponse:
    if USE_KEYCLOAK:
        if request.user and request.user.is_authenticated:
            return redirect(request.GET.get("next", DEFAULT_REDIRECT_URI))
        redirect_url = (
            f"{KEYCLOAK_URL_BASE_CLIENT}realms/{REALM_NAME}/protocol/openid-connect/auth"
            f"?client_id={CLIENT_ID}&response_type=code&scope={'%20'.join(KEYCLOAK_SCOPES or [])}"
            f"&redirect_uri={KEYCLOAK_REDIRECT_URI}"
        )

        return redirect(redirect_url)
    msg = None
    if request.method == "POST":
        login_form = LoginForm(request.POST)
        if login_form.is_valid():
            username = login_form.cleaned_data.get("username")
            password = login_form.cleaned_data.get("password")
            user = authenticate(username=username, password=password)
            if user is not None:
                if request.user:
                    logout(request)
                login(request, user)

                logger.info("Очистка устаревших сессий...")
                clear_previos_sessions(request)

                next = None
                if CACHE_CALLBACK:
                    next = CACHE_CALLBACK(request)

                logger.info("Пользователь %s авторизован", username)
                return redirect(request.GET.get("next", next or DEFAULT_REDIRECT_URI))
            logger.warning("Пользователь %s ввёл неверные учётные данные", username)
            msg = "Неправильный логин или пароль"
        else:
            logger.info("Форма авторизации не прошла проверку")
            msg = "Некорректный ввод"
    else:
        user = request.user
        if user and user.is_authenticated:
            logger.info("Пользователь %s уже авторизован.", user.get_username())
            return redirect(request.GET.get("next", DEFAULT_REDIRECT_URI))

    form = LoginForm(request.POST or None)
    return render(
        request,
        AUTH_PAGE_DOCUMENT or "templates/login.html",
        context={
            "app_name": "authentication",
            "title": "Вход",
            "center": True,
            "msg": msg,
            "version": VERSION_TAG,
            "form": form,
        },
    )


def _get_code(request: HttpRequest) -> str:
    code = request.GET.get("code")
    if not code:
        msg = "Не предоставлен code в обратном вызове Keycloak"  # noqa: RUF001
        raise KeyError(msg)
    return code


def keycloak_callback(request: HttpRequest) -> HttpResponse | HttpResponseRedirect | HttpResponsePermanentRedirect:
    logger.info("Обратный вызов из Keycloak")
    try:
        code = _get_code(request)
    except Exception:
        return render(
            request,
            ERROR_PAGE_DOCUMENT or "templates/500.html",
            {
                "code": 500,
                "message": "Ошибка аутентификации в Keycloak, обратный вызов не вернул код, пожалуйста, \
                    обратитесь к администратору!",
                "header": "Ошибка аутентификации Keycloak",
                "button_text": "Вернуться к авторизации",
                "logout_url": reverse_lazy("yarik_django_auth:login"),
            },
        )
    logger.info("Авторизационный код получен")
    logger.debug("Код: %s", code)

    backend = KeycloakConfidentialBackend()
    logger.info("Обмен кода на токен")
    data_token = backend.exchange_code_for_token(code)

    if not data_token:
        logger.warning("Ошибка: токен не получен")
        return render(
            request,
            ERROR_PAGE_DOCUMENT or "templates/403.html",
            {
                "code": 500,
                "message": "Ошибка аутентификации в Keycloak, не удалось получить токен, пожалуйста, \
                    обратитесь к администратору!",
                "header": "Ошибка аутентификации Keycloak",
                "button_text": "Вернуться к авторизации",
                "logout_url": reverse_lazy("yarik_django_auth:login"),
            },
        )

    logger.info("Токен получен")
    logger.info("Начало аутентификации")

    try:
        user = backend.authenticate(data_token)
    except jwt.exceptions.ImmatureSignatureError:
        logger.exception(
            "Ошибка проверки подписи токена, токен предоставляет время в будущем, синхронизируйте системное время"
        )
        return render(
            request,
            ERROR_PAGE_DOCUMENT or "templates/500.html",
            {
                "code": 500,
                "message": "Токен не прошёл проверку, пожалуйста обратитесь к администратору!",
                "header": "Ошибка аутентификации в Keycloak",
                "button_text": "Выход",
                "logout_url": f"{KEYCLOAK_URL_BASE_CLIENT}realms/{REALM_NAME}/protocol/openid-connect/logout"
                f"?client_id={CLIENT_ID}&post_logout_redirect_uri={KEYCLOAK_POST_LOGOUT_URL}",
            },
        )
    except PermissionError:
        logger.exception("У клиента нет доступа к интерфейсу")  # noqa: RUF001
        return render(
            request,
            ERROR_PAGE_DOCUMENT or "templates/403.html",
            {
                "code": 403,
                "message": "У данного пользователя нет доступа к интерфейсу, пожалуйста обратитесь к администратору!",  # noqa: RUF001
                "header": "Ошибка аутентификации в Keycloak",
                "button_text": "Выход",
                "logout_url": f"{KEYCLOAK_URL_BASE_CLIENT}realms/{REALM_NAME}/protocol/openid-connect/logout"
                f"?client_id={CLIENT_ID}&post_logout_redirect_uri={KEYCLOAK_POST_LOGOUT_URL}",
            },
        )
    except Exception as e:
        logger.exception("Неизвестная ошибка при декодировании токена")
        logger.debug(e.args)
        return render(
            request,
            ERROR_PAGE_DOCUMENT or "templates/520.html",
            {
                "code": 520,
                "message": "Неизвестная ошибка, пожалуйста обратитесь к администратору!",
                "header": "Ошибка аутентификации в Keycloak",
                "button_text": "Выход",
                "logout_url": f"{KEYCLOAK_URL_BASE_CLIENT}realms/{REALM_NAME}/protocol/openid-connect/logout"
                f"?client_id={CLIENT_ID}&post_logout_redirect_uri={KEYCLOAK_POST_LOGOUT_URL}",
            },
        )

    if user is not None:
        login(request, user, backend="django.contrib.auth.backends.ModelBackend")

        logger.info("Очистка устаревших сессий...")
        clear_previos_sessions(request)
        next = None
        if CACHE_CALLBACK:
            next = CACHE_CALLBACK(request)

        logger.info("Пользователь %s авторизован.", user.username)
        request.session["oidc_auth_token"] = data_token
        return redirect(request.GET.get("next", next or DEFAULT_REDIRECT_URI))
    return render(
        request,
        ERROR_PAGE_DOCUMENT or "templates/404.html",
        {
            "code": 404,
            "message": "Аутентификация в Keycloak прошла, однако данного пользователя не существует в системе, \
                пожалуйста обратитесь к администратору!",
            "header": "Ошибка авторизации",
            "button_text": "Выход",
            "logout_url": f"{KEYCLOAK_URL_BASE_CLIENT}realms/{REALM_NAME}/protocol/openid-connect/logout"
            f"?client_id={CLIENT_ID}&post_logout_redirect_uri={KEYCLOAK_POST_LOGOUT_URL}",
        },
    )


@login_required(login_url=reverse_lazy("yarik_django_auth:login"))
def logout_view(request: HttpRequest) -> HttpResponseRedirect | HttpResponsePermanentRedirect:
    user = request.user
    if CLEAR_CACHE_CALLBACK and isinstance(user, User):
        CLEAR_CACHE_CALLBACK(user)
    logout(request)
    logger.info("Пользователь %s вышел из системы", user.get_username())
    if USE_KEYCLOAK:
        redirect_url = (
            f"{KEYCLOAK_URL_BASE_CLIENT}realms/{REALM_NAME}/protocol/openid-connect/logout"
            f"?client_id={CLIENT_ID}&post_logout_redirect_uri={KEYCLOAK_POST_LOGOUT_URL}"
        )

        return redirect(redirect_url)
    return redirect(reverse_lazy("yarik_django_auth:login"))


@ajax_login_required
def get_new_csrftoken(request: HttpRequest) -> JsonResponse:
    token = get_token(request)
    return JsonResponse({"token": token})
