"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 26.04.24

"""
import base64
import hashlib
from urllib.parse import urlencode

import requests
from django.contrib.auth import login
from django.http import HttpRequest
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.timezone import now
from django.views import View

from api.models import OAuthToken
from konova.sub_settings.django_settings import BASE_URL
from konova.sub_settings.sso_settings import SSO_SERVER_BASE, OAUTH_CODE_VERIFIER, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET


class OAuthLoginView(View):
    """
    Starts OAuth Login procedure
        -> AnonymousUser is redirected to SSO component using specific parameters
        -> After successful login (in SSO component), user will be redirected to a specific callback url (OAuthCallbackView)
        -> Callback view uses retrieved authorization token to get a proper access token from SSO component
        -> SSO component answers with access token
        -> OAuthCallbackView uses token in Authorization header to access user data of logged-in user in SSO component
        -> OAuthCallbackView creates/updates user
        -> OAuthCallbackView logs in user and redirects to default home view

    """

    def __create_code_challenge(self):
        """
        Creates a code verifier and code challenge for extra security.
        See https://django-oauth-toolkit.readthedocs.io/en/latest/getting_started.html#authorization-code for further
        information

        Returns:

        """
        code_verifier = OAUTH_CODE_VERIFIER

        code_challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest()
        code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8').replace('=', '')
        return code_verifier, code_challenge

    def get(self, request: HttpRequest, *args, **kwargs):
        """
        Redirects user to OAuth SSO webservice for credential based login there

        Args:
            request ():
            *args ():
            **kwargs ():

        Returns:

        """
        oauth_authentication_code_url = f"{SSO_SERVER_BASE}o/authorize/"
        redirect_uri = f'{BASE_URL}{reverse("oauth-callback")}'

        code_verifier, code_challenge = self.__create_code_challenge()

        urlencode_params = urlencode(
            {
                "response_type": "code",
                "code_challenge": code_challenge,
                "code_challenge_method": "S256",
                "client_id": OAUTH_CLIENT_ID,
                "redirect_uri": redirect_uri,
            }
        )
        url = f"{oauth_authentication_code_url}?{urlencode_params}"
        return redirect(url)


class OAuthCallbackView(View):
    """
    Callback view for OAuth2.0 authentication token.
    Authentication tokens will be exchanged for access token.
    Access Token will be used for fetching user data from SSO component.
    User data will be used for creating/updating user data inside this app.
    User will be logged-in and redirected to default home view.

    """

    def get(self, request: HttpRequest, *args, **kwargs):
        authentication_code = request.GET.get("code")
        oauth_acces_token_url = f"{SSO_SERVER_BASE}o/token/"

        callback_url = f'{BASE_URL}{reverse("oauth-callback")}'

        params = {
            "grant_type": "authorization_code",
            "code": authentication_code,
            "redirect_uri": callback_url,
            "code_verifier": OAUTH_CODE_VERIFIER,
            "client_id": OAUTH_CLIENT_ID,
            "client_secret": OAUTH_CLIENT_SECRET
        }
        access_code_response = requests.post(
            oauth_acces_token_url,
            data=params
        )
        received_on = now()

        access_code_response_body = access_code_response.content.decode("utf-8")
        status_code_invalid = access_code_response.status_code != 200
        if status_code_invalid:
            raise RuntimeError(f"OAuth access token could not be fetched: {access_code_response.text}")

        oauth_token = OAuthToken.from_access_token_response(access_code_response_body, received_on)
        oauth_token.save()
        user = oauth_token.update_and_get_user()
        user.oauth_replace_token(oauth_token)

        login(request, user)
        return redirect("home")

