import unicodedata
import logging

from django.contrib.auth.models import User
from django.core.exceptions import SuspiciousOperation
from django.urls import reverse
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.conf import settings

from mozilla_django_oidc.auth import OIDCAuthenticationBackend
from mozilla_django_oidc.utils import absolutify, import_from_settings

from ..models.member import Member

logger = logging.getLogger(__name__)


def generate_username(email):

    return unicodedata.normalize("NFKC", email.split("@")[0][:150])


def store_tokens(session, access_token, id_token, refresh_token):
    if import_from_settings("OIDC_STORE_ACCESS_TOKEN", False):
        session["oidc_access_token"] = access_token

    if import_from_settings("OIDC_STORE_ID_TOKEN", False):
        session["oidc_id_token"] = id_token

    if import_from_settings("OIDC_STORE_REFRESH_TOKEN", True):
        session["oidc_refresh_token"] = refresh_token


class OIDCAuthenticationBackendWithRefresh(OIDCAuthenticationBackend):
    """
    Modified version based on https://github.com/mozilla/mozilla-django-oidc/pull/377
    to add refresh token storage as part of the authentication process.

    They can be used later in the session refresh middleware

    """

    def authenticate(self, request, **kwargs):
        """Authenticates a user based on the OIDC code flow."""

        self.request = request
        if not self.request:
            return None

        state = self.request.GET.get("state")
        code = self.request.GET.get("code")
        nonce = kwargs.pop("nonce", None)
        code_verifier = kwargs.pop("code_verifier", None)

        if not code or not state:
            return None

        reverse_url = self.get_settings(
            "OIDC_AUTHENTICATION_CALLBACK_URL", "oidc_authentication_callback"
        )

        token_payload = {
            "client_id": self.OIDC_RP_CLIENT_ID,
            "client_secret": self.OIDC_RP_CLIENT_SECRET,
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": absolutify(self.request, reverse(reverse_url)),
        }

        if code_verifier is not None:
            token_payload.update({"code_verifier": code_verifier})

        # Get the token
        token_info = self.get_token(token_payload)
        id_token = token_info.get("id_token")
        access_token = token_info.get("access_token")
        refresh_token = token_info.get("refresh_token")

        # Validate the token
        payload = self.verify_token(id_token, nonce=nonce)
        if payload:
            session = self.request.session
            store_tokens(session, access_token, id_token, refresh_token)
            try:
                return self.get_or_create_user(access_token, id_token, payload)
            except SuspiciousOperation as exc:
                logger.warning("Failed to get or create user: %s", exc)
                return None

        return None


class MemberOIDCAuthenticationBackend(OIDCAuthenticationBackendWithRefresh):

    def __init__(self, *args, **kwargs):
        self.default_scopes = ("openid", "email", "profile")
        super().__init__(self, args, kwargs)

    def get_scopes(self):
        env_settings = self.get_settings("OIDC_RP_SCOPES", None)
        if env_settings:
            return env_settings.split()
        return self.default_scopes

    def verify_claims(self, claims):
        if "email" not in claims:
            return False
        return True

    def get_username(self, claims):
        if "preferred_username" in claims:
            logger.info("Using preferred user name")
            return claims["preferred_username"]
        return generate_username(claims.get("email"))

    def create_user(self, claims):
        logger.info("Creating new user")

        user = super().create_user(claims)

        logger.info("Updating member based on OIDC claims")
        member = Member.objects.get(id=user.id)

        # Assuming email verification etc has happened on IDP
        member.verified = True
        member.first_name = claims.get("given_name", "")
        member.last_name = claims.get("family_name", "")
        member.save()

        if settings.EMAIL_ON_SELF_REGISTER:
            self.email_new_user(user)
        return user

    def email_new_user(self, user):

        context = {
            "email": user.email,
        }

        emails_dir = "ichec_django_core/emails"

        template = emails_dir + "/admin_user_not_invited"
        text = render_to_string(template + ".txt", context=context)
        html = render_to_string(template + ".html", context=context)

        superusers_emails = User.objects.filter(is_superuser=True).values_list("email")
        if superusers_emails:
            email = superusers_emails[0]
        else:
            return

        msg = EmailMultiAlternatives(
            subject="New Self-Registered User",
            body=text,
            from_email=settings.DEFAULT_EMAIL_SENDER,
            to=[email],
            headers={"List-Unsubscribe": f"<mailto:{settings.DEFAULT_EMAIL_UNSUB}"},
        )

        msg.attach_alternative(html, "text/html")
        msg.send()

    def email_invited_user(self, user):
        context = {
            "email": user.email,
        }

        emails_dir = "ichec_django_core/emails"

        template = emails_dir + "/admin_user_invited"
        text = render_to_string(template + ".txt", context=context)
        html = render_to_string(template + ".html", context=context)

        superusers_emails = User.objects.filter(is_superuser=True).values_list("email")
        if superusers_emails:
            email = superusers_emails[0]
        else:
            return

        msg = EmailMultiAlternatives(
            subject="New Invited User",
            body=text,
            from_email=settings.DEFAULT_EMAIL_SENDER,
            to=[email],
            headers={"List-Unsubscribe": f"<mailto:{settings.DEFAULT_EMAIL_UNSUB}"},
        )

        msg.attach_alternative(html, "text/html")
        msg.send()

    def update_user(self, user, claims):
        member = Member.objects.get(id=user.id)

        # Assuming email verification etc has now happened on IDP
        if not member.verified:
            member.verified = True
            member.first_name = claims.get("given_name", "")
            member.last_name = claims.get("family_name", "")
            member.save()

            if settings.EMAIL_ON_SELF_REGISTER:
                self.email_invited_user(user)

        return user
