from typing import Optional

from django.forms import ValidationError

from allauth.account import app_settings as account_settings
from allauth.account.adapter import get_adapter as get_account_adapter
from allauth.account.internal.flows.manage_email import assess_unique_email
from allauth.account.internal.flows.signup import complete_signup, prevent_enumeration
from allauth.account.utils import user_username
from allauth.core.exceptions import SignupClosedException
from allauth.core.internal.httpkit import headed_redirect_response
from allauth.socialaccount import app_settings
from allauth.socialaccount.adapter import get_adapter
from allauth.socialaccount.models import SocialLogin


def get_pending_signup(request) -> Optional[SocialLogin]:
    if data := request.session.get("socialaccount_sociallogin"):
        try:
            return SocialLogin.deserialize(data)
        except ValueError:
            return None
    return None


def redirect_to_signup(request, sociallogin):
    request.session["socialaccount_sociallogin"] = sociallogin.serialize()
    return headed_redirect_response("socialaccount_signup")


def clear_pending_signup(request):
    request.session.pop("socialaccount_sociallogin", None)


def signup_by_form(request, sociallogin, form):
    clear_pending_signup(request)
    user, resp = form.try_save(request)
    if not resp:
        resp = complete_social_signup(request, sociallogin)
    return resp


def process_auto_signup(request, sociallogin):
    auto_signup = get_adapter().is_auto_signup_allowed(request, sociallogin)
    if not auto_signup:
        return False, None
    auto_signup, resp = process_auto_signup_email(request, sociallogin)
    if not auto_signup:
        return False, resp
    auto_signup, resp = process_auto_signup_phone(request, sociallogin)
    return auto_signup, resp


def process_auto_signup_email(request, sociallogin):
    email = None
    if sociallogin.email_addresses:
        email = sociallogin.email_addresses[0].email
    # Let's check if auto_signup is really possible...
    if email:
        assessment = assess_unique_email(email)
        if assessment is True:
            # Auto signup is fine.
            auto_signup = True
        elif assessment is False:
            # Oops, another user already has this address.  We cannot simply
            # connect this social account to the existing user. Reason is
            # that the email address may not be verified, meaning, the user
            # may be a hacker that has added your email address to their
            # account in the hope that you fall in their trap.  We cannot
            # check on 'email_address.verified' either, because
            # 'email_address' is not guaranteed to be verified.
            auto_signup = False
            # TODO: We redirect to signup form -- user will see email
            # address conflict only after posting whereas we detected it
            # here already.
        else:
            assert assessment is None  # nosec
            # Prevent enumeration is properly turned on, meaning, we cannot
            # show the signup form to allow the user to input another email
            # address. Instead, we're going to send the user an email that
            # the account already exists, and on the outside make it appear
            # as if an email verification mail was sent.
            resp = prevent_enumeration(request, email=email)
            return False, resp
    elif app_settings.EMAIL_REQUIRED:
        # Nope, email is required and we don't have it yet...
        auto_signup = False
    else:
        auto_signup = True
    return auto_signup, None


def process_auto_signup_phone(request, sociallogin):
    # At this point, email is not required, or, we have a unique email.  Let's
    # check the phone number.
    phone_field = account_settings.SIGNUP_FIELDS.get("phone")
    if not phone_field or not phone_field["required"]:
        return True, None
    if not sociallogin.phone:
        return False, None
    # If the phone is already taken?
    existing_user = get_account_adapter().get_user_by_phone(sociallogin.phone)
    if existing_user:
        return False, None
    return True, None


def process_signup(request, sociallogin):
    if not get_adapter().is_open_for_signup(request, sociallogin):
        raise SignupClosedException()
    auto_signup, resp = process_auto_signup(request, sociallogin)
    if resp:
        return resp
    if not auto_signup:
        resp = redirect_to_signup(request, sociallogin)
    else:
        # Ok, auto signup it is, at least the email address is ok.
        # We still need to check the username though...
        if account_settings.USER_MODEL_USERNAME_FIELD:
            username = user_username(sociallogin.user)
            try:
                get_account_adapter(request).clean_username(username)
            except ValidationError:
                # This username is no good ...
                user_username(sociallogin.user, "")
        # TODO: This part contains a lot of duplication of logic
        # ("closed" rendering, create user, send email, in active
        # etc..)
        get_adapter().save_user(request, sociallogin, form=None)
        resp = complete_social_signup(request, sociallogin)
    return resp


def complete_social_signup(request, sociallogin):
    from allauth.socialaccount.internal.flows.login import record_authentication

    record_authentication(request, sociallogin)
    return complete_signup(
        request,
        user=sociallogin.user,
        email_verification=app_settings.EMAIL_VERIFICATION,
        redirect_url=sociallogin.get_redirect_url(request),
        signal_kwargs={"sociallogin": sociallogin},
    )
