import logging
import re
from operator import itemgetter
from urllib.parse import quote

from django import forms
from django.contrib.auth.models import User
from django.db import transaction
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from requests import HTTPError
from reversion import revisions
from social_core.backends.github import GithubOAuth2
from social_core.exceptions import InvalidEmail, SocialAuthBaseException
from social_core.pipeline.partial import partial
from social_django.middleware import (
    SocialAuthExceptionMiddleware as OldSocialAuthExceptionMiddleware,
)

from judge.forms import ProfileForm
from judge.models import Language, Profile

logger = logging.getLogger("judge.social_auth")


class GitHubSecureEmailOAuth2(GithubOAuth2):
    name = "github-secure"

    def user_data(self, access_token, *args, **kwargs):
        data = self._user_data(access_token)
        try:
            emails = self._user_data(access_token, "/emails")
        except (HTTPError, ValueError, TypeError):
            emails = []

        emails = [
            (e.get("email"), e.get("primary"), 0)
            for e in emails
            if isinstance(e, dict) and e.get("verified")
        ]
        emails.sort(key=itemgetter(1), reverse=True)
        emails = list(map(itemgetter(0), emails))

        if emails:
            data["email"] = emails[0]
        else:
            data["email"] = None

        return data


def slugify_username(username, renotword=re.compile(r"[^\w]")):
    return renotword.sub("", username.replace("-", "_"))


def verify_email(backend, details, *args, **kwargs):
    if not details["email"]:
        raise InvalidEmail(backend)


class UsernameForm(forms.Form):
    username = forms.RegexField(
        regex=r"^\w+$",
        max_length=30,
        label="Username",
        error_messages={
            "invalid": "A username must contain letters, numbers, or underscores"
        },
    )

    def clean_username(self):
        if User.objects.filter(username=self.cleaned_data["username"]).exists():
            raise forms.ValidationError("Sorry, the username is taken.")
        return self.cleaned_data["username"]


@partial
def choose_username(backend, user, username=None, *args, **kwargs):
    if not user:
        request = backend.strategy.request
        if request.POST:
            form = UsernameForm(request.POST)
            if form.is_valid():
                return {"username": form.cleaned_data["username"]}
        else:
            form = UsernameForm(initial={"username": username})
        return render(
            request,
            "registration/username_select.html",
            {
                "title": "Choose a username",
                "form": form,
            },
        )


@partial
def make_profile(backend, user, response, is_new=False, *args, **kwargs):
    if is_new:
        if not hasattr(user, "profile"):
            profile = Profile(user=user)
            profile.language = Language.get_default_language()
            logger.info("Info from %s: %s", backend.name, response)
            profile.save()
            form = ProfileForm(instance=profile, user=user)
        else:
            data = backend.strategy.request_data()
            logger.info(data)
            form = ProfileForm(data, instance=user.profile, user=user)
            if form.is_valid():
                with transaction.atomic(), revisions.create_revision():
                    form.save()
                    revisions.set_user(user)
                    revisions.set_comment("Updated on registration")
                    return
        return render(
            backend.strategy.request,
            "registration/profile_creation.html",
            {
                "title": "Create your profile",
                "form": form,
            },
        )


class SocialAuthExceptionMiddleware(OldSocialAuthExceptionMiddleware):
    def process_exception(self, request, exception):
        if isinstance(exception, SocialAuthBaseException):
            return HttpResponseRedirect(
                "%s?message=%s"
                % (
                    reverse("social_auth_error"),
                    quote(self.get_message(request, exception)),
                )
            )
