import logging
import random
from collections.abc import Iterable
from contextlib import suppress
from typing import Annotated, Any, cast
from urllib.parse import urlencode, urljoin

import orjson
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, get_backends
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sessions.backends.base import SessionBase
from django.core import validators
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import Q
from django.db.utils import IntegrityError
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect, render
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.translation import get_language, gettext_lazy
from django.utils.translation import gettext as _
from django.utils.translation import override as override_language
from django.views.decorators.cache import never_cache
from django_auth_ldap.backend import LDAPBackend, _LDAPUser
from pydantic import Json, NonNegativeInt, StringConstraints

from confirmation import settings as confirmation_settings
from confirmation.models import (
    Confirmation,
    ConfirmationKeyError,
    create_confirmation_link,
    get_object_from_key,
    render_confirmation_key_error,
)
from zerver.actions.create_realm import (
    DEFAULT_EMAIL_ADDRESS_VISIBILITY_FOR_REALM,
    do_create_realm,
    get_email_address_visibility_default,
)
from zerver.actions.create_user import do_activate_mirror_dummy_user, do_create_user
from zerver.actions.default_streams import lookup_default_stream_groups
from zerver.actions.user_settings import (
    do_change_full_name,
    do_change_password,
    do_change_user_delivery_email,
    do_change_user_setting,
)
from zerver.actions.users import do_change_user_role, generate_password_reset_url
from zerver.context_processors import (
    get_realm_create_form_context,
    get_realm_from_request,
    is_realm_import_enabled,
    login_context,
)
from zerver.decorator import add_google_analytics, do_login, require_post
from zerver.forms import (
    HOW_FOUND_ZULIP_EXTRA_CONTEXT,
    CaptchaDemoRegistrationForm,
    CaptchaRealmCreationForm,
    DemoRegistrationForm,
    FindMyTeamForm,
    HomepageForm,
    ImportRealmOwnerSelectionForm,
    RealmCreationForm,
    RealmRedirectForm,
    RegistrationForm,
    check_subdomain_available,
)
from zerver.lib.demo_organizations import get_demo_organization_wordlists
from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm
from zerver.lib.exceptions import JsonableError, RateLimitedError
from zerver.lib.i18n import (
    get_browser_language_code,
    get_default_language_for_anonymous_user,
    get_default_language_for_new_user,
    get_language_name,
)
from zerver.lib.pysa import mark_sanitized
from zerver.lib.queue import queue_json_publish_rollback_unsafe
from zerver.lib.rate_limiter import rate_limit_request_by_ip
from zerver.lib.response import json_success
from zerver.lib.send_email import EmailNotDeliveredError, FromAddress, send_email
from zerver.lib.sessions import get_expirable_session_var
from zerver.lib.subdomains import get_subdomain
from zerver.lib.typed_endpoint import (
    ApiParamConfig,
    PathOnly,
    typed_endpoint,
    typed_endpoint_without_parameters,
)
from zerver.lib.typed_endpoint_validators import (
    check_int_in_validator,
    non_negative_int_or_none_validator,
    timezone_or_empty_validator,
)
from zerver.lib.upload import all_message_attachments
from zerver.lib.url_encoding import append_url_query_string
from zerver.lib.users import get_accounts_for_email
from zerver.models import (
    Message,
    MultiuseInvite,
    NamedUserGroup,
    PreregistrationRealm,
    PreregistrationUser,
    Realm,
    RealmUserDefault,
    Stream,
    UserProfile,
)
from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH
from zerver.models.prereg_users import RealmCreationStatus
from zerver.models.realm_audit_logs import AuditLogEventType, RealmAuditLog
from zerver.models.realms import (
    DisposableEmailError,
    DomainNotAllowedForRealmError,
    EmailContainsPlusError,
    get_org_type_display_name,
    get_realm,
    name_changes_disabled,
)
from zerver.models.streams import get_default_stream_groups
from zerver.models.users import (
    get_source_profile,
    get_user_by_delivery_email,
    get_user_profile_by_id_in_realm,
)
from zerver.views.auth import (
    create_preregistration_realm,
    create_preregistration_user,
    finish_desktop_flow,
    finish_mobile_flow,
    get_safe_redirect_to,
    redirect_and_log_into_subdomain,
    redirect_to_deactivation_notice,
)
from zerver.views.errors import config_error
from zproject.backends import (
    ExternalAuthResult,
    NoMatchingLDAPUserError,
    ZulipLDAPAuthBackend,
    email_auth_enabled,
    email_belongs_to_ldap,
    get_external_method_dicts,
    ldap_auth_enabled,
    password_auth_enabled,
)

logger = logging.getLogger("")


@typed_endpoint
def get_prereg_key_and_redirect(
    request: HttpRequest, *, confirmation_key: PathOnly[str], full_name: str | None = None
) -> HttpResponse:
    """
    The purpose of this little endpoint is primarily to take a GET
    request to a long URL containing a confirmation key, and render
    a page that will via JavaScript immediately do a POST request to
    /accounts/register, so that the user can create their account on
    a page with a cleaner URL (and with the browser security and UX
    benefits of an HTTP POST having generated the page).

    The only thing it does before rendering that page is to check
    the validity of the confirmation link. This is redundant with a
    similar check in accounts_register, but it provides a slightly nicer
    user-facing error handling experience if the URL you visited is
    displayed in the browser. (E.g. you can debug that you
    accidentally adding an extra character after pasting).
    """
    try:
        prereg_object, realm_creation = check_prereg_key(request, confirmation_key)
    except ConfirmationKeyError as e:
        return render_confirmation_key_error(request, e)

    registration_url = reverse("accounts_register")
    if realm_creation:
        assert isinstance(prereg_object, PreregistrationRealm)
        if prereg_object.data_import_metadata.get("import_from") == "slack":
            registration_url = reverse("import_realm_from_slack")
        else:
            registration_url = reverse("realm_register")

    return render(
        request,
        "confirmation/confirm_preregistrationuser.html",
        context={
            "key": confirmation_key,
            "full_name": full_name,
            "registration_url": registration_url,
        },
    )


def check_prereg_key(
    request: HttpRequest, confirmation_key: str
) -> tuple[PreregistrationUser | PreregistrationRealm, bool]:
    """
    Checks if the Confirmation key is valid, returning the PreregistrationUser or
    PreregistrationRealm object in case of success and raising an appropriate
    ConfirmationKeyError otherwise.
    """
    confirmation_types = [
        Confirmation.USER_REGISTRATION,
        Confirmation.INVITATION,
        Confirmation.NEW_REALM_USER_REGISTRATION,
    ]

    prereg_object = get_object_from_key(
        confirmation_key, confirmation_types, mark_object_used=False
    )
    assert isinstance(prereg_object, PreregistrationRealm | PreregistrationUser)

    confirmation_obj = prereg_object.confirmation.get()
    realm_creation = confirmation_obj.type == Confirmation.NEW_REALM_USER_REGISTRATION

    if realm_creation:
        assert isinstance(prereg_object, PreregistrationRealm)
        if prereg_object.data_import_metadata.get("need_select_realm_owner"):
            # Allow user to get back to the import page to select realm owner.
            # This is for a special case where the realm import has finished
            # but user closed the import process browser window and needs to
            # get back to the import page from the email confirmation link.
            assert prereg_object.created_realm is not None
        else:
            # Defensive assert to make sure no mix-up in how .status is set leading to reuse
            # of a PreregistrationRealm object.
            assert prereg_object.created_realm is None
    else:
        assert isinstance(prereg_object, PreregistrationUser)
        # Defensive assert to make sure no mix-up in how .status is set leading to reuse
        # of a PreregistrationUser object.
        assert prereg_object.created_user is None

    return prereg_object, realm_creation


def get_selected_realm_type_name(prereg_realm: PreregistrationRealm | None) -> str | None:
    if prereg_realm is None:
        # We show the selected realm type only when creating new realm.
        return None

    return get_org_type_display_name(prereg_realm.org_type)


def get_selected_realm_default_language_name(
    prereg_realm: PreregistrationRealm | None,
) -> str | None:
    if prereg_realm is None:
        # We show the selected realm language only when creating new realm.
        return None

    return get_language_name(prereg_realm.default_language)


@add_google_analytics
@require_post
def realm_register(*args: Any, **kwargs: Any) -> HttpResponse:
    return registration_helper(*args, **kwargs)


@require_post
def accounts_register(*args: Any, **kwargs: Any) -> HttpResponse:
    return registration_helper(*args, **kwargs)


@never_cache
@require_post
def import_realm_from_slack(*args: Any, **kwargs: Any) -> HttpResponse:
    return registration_helper(*args, **kwargs)


@typed_endpoint
def registration_helper(
    request: HttpRequest,
    *,
    cancel_import: Json[bool] = False,
    form_full_name: Annotated[str | None, ApiParamConfig("full_name")] = None,
    from_confirmation: str | None = None,
    key: str = "",
    slack_access_token: str | None = None,
    source_realm_id: Annotated[NonNegativeInt | None, non_negative_int_or_none_validator()] = None,
    start_slack_import: Json[bool] = False,
    timezone: Annotated[str, timezone_or_empty_validator()] = "",
    email_address_visibility: Annotated[
        Json[int], check_int_in_validator(RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_TYPES)
    ] = DEFAULT_EMAIL_ADDRESS_VISIBILITY_FOR_REALM,
) -> HttpResponse:
    try:
        prereg_object, realm_creation = check_prereg_key(request, key)
    except ConfirmationKeyError as e:
        return render_confirmation_key_error(request, e)

    email = prereg_object.email
    prereg_realm = None
    prereg_user = None
    if realm_creation:
        assert isinstance(prereg_object, PreregistrationRealm)
        prereg_realm = prereg_object

        if cancel_import:
            if prereg_realm.created_realm or prereg_realm.data_import_metadata.get(
                "is_import_work_queued"
            ):
                # This cancellation flow is just to go back to normal
                # realm creation before one has started it. If the
                # user somehow triggers this operation (no longer
                # visible in the UI) after that point, we just
                # redirect user import status page with a message that
                # import work has already started.
                return TemplateResponse(
                    request,
                    "zerver/slack_import.html",
                    {
                        "import_poll_error_message": _(
                            "Unable to cancel import once it has started."
                        ),
                        "poll_for_import_completion": True,
                        "key": key,
                    },
                )

            # If the user cancels the import process, it is critical
            # to remove any metadata that was added during the import
            # process.
            # NOTE: Don't revoke the confirmation key here, to allow
            # the user to continue with the registration process if
            # no import flow has started. This is important otherwise
            # the user will be unable to register using the same subdomain.
            prereg_realm.data_import_metadata = {}
            prereg_realm.save(update_fields=["data_import_metadata"])
            # Return back to normal registration flow.
            return HttpResponseRedirect(
                reverse("get_prereg_key_and_redirect", kwargs={"confirmation_key": key})
            )

        if start_slack_import:
            assert is_realm_import_enabled()
            assert prereg_realm.data_import_metadata.get("slack_access_token") is not None
            assert prereg_realm.data_import_metadata.get("uploaded_import_file_name") is not None
            assert prereg_realm.data_import_metadata.get("is_import_work_queued") is not True
            assert prereg_realm.created_realm is None

            prereg_realm.data_import_metadata["email_address_visibility"] = email_address_visibility
            prereg_realm.save(update_fields=["data_import_metadata"])

            queue_json_publish_rollback_unsafe(
                "deferred_work",
                {
                    "type": "import_slack_data",
                    "preregistration_realm_id": prereg_realm.id,
                    "filename": f"import/{prereg_realm.id}/slack.zip",
                    "slack_access_token": prereg_realm.data_import_metadata["slack_access_token"],
                },
            )
            # Avoid starting the import process multiple times.
            prereg_realm.data_import_metadata["is_import_work_queued"] = True
            prereg_realm.save(update_fields=["data_import_metadata"])

            return TemplateResponse(
                request,
                "zerver/slack_import.html",
                {
                    "poll_for_import_completion": True,
                    "key": key,
                },
            )

        elif prereg_realm.data_import_metadata.get("import_from") == "slack":
            if prereg_realm.data_import_metadata.get("user_activation_url"):
                assert prereg_realm.data_import_metadata["need_select_realm_owner"] is True
                return HttpResponseRedirect(
                    prereg_realm.data_import_metadata["user_activation_url"]
                )
            if prereg_realm.data_import_metadata.get("need_select_realm_owner"):
                return HttpResponseRedirect(
                    reverse("realm_import_post_process", kwargs={"confirmation_key": key})
                )
            if prereg_realm.data_import_metadata.get("is_import_work_queued"):
                return TemplateResponse(
                    request,
                    "zerver/slack_import.html",
                    {
                        "poll_for_import_completion": True,
                        "key": key,
                    },
                )

            # Set text of `EMAIL_ADDRESS_VISIBILITY_EVERYONE` to "Everyone" so that it doesn't overflow the
            # select box in the slack import page.
            email_address_visibility_options = []

            for id, name in RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_ID_TO_NAME_MAP.items():
                if id == RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_EVERYONE:
                    name = gettext_lazy("Everyone")
                email_address_visibility_options.append((id, name))

            assert is_realm_import_enabled()
            context: dict[str, Any] = {
                "key": key,
                "max_file_size": settings.MAX_WEB_DATA_IMPORT_SIZE_MB,
                "email_address_visibility_options": email_address_visibility_options,
                "email_address_visibility_default": get_email_address_visibility_default(
                    prereg_realm.org_type
                ),
            }

            saved_slack_access_token = prereg_realm.data_import_metadata.get("slack_access_token")
            if saved_slack_access_token or slack_access_token is not None:
                if (
                    slack_access_token is not None
                    and slack_access_token != saved_slack_access_token
                ):
                    # Verify slack token access.
                    from zerver.data_import.slack import (
                        SLACK_IMPORT_TOKEN_SCOPES,
                        check_slack_token_access,
                    )

                    try:
                        check_slack_token_access(slack_access_token, SLACK_IMPORT_TOKEN_SCOPES)
                    except Exception as e:
                        context["slack_access_token_validation_error"] = str(e)
                        return TemplateResponse(
                            request,
                            "zerver/slack_import.html",
                            context,
                        )

                    saved_slack_access_token = slack_access_token
                    prereg_realm.data_import_metadata["slack_access_token"] = slack_access_token
                    prereg_realm.save(update_fields=["data_import_metadata"])

                context["slack_access_token"] = saved_slack_access_token
                context["uploaded_import_file_name"] = prereg_realm.data_import_metadata.get(
                    "uploaded_import_file_name"
                )
                context["invalid_file_error_message"] = prereg_realm.data_import_metadata.get(
                    "invalid_file_error_message", ""
                )

            return TemplateResponse(
                request,
                "zerver/slack_import.html",
                context,
            )

        password_required = True
        role = UserProfile.ROLE_REALM_OWNER
    else:
        assert isinstance(prereg_object, PreregistrationUser)
        prereg_user = prereg_object
        password_required = prereg_object.password_required
        role = prereg_object.invited_as

    try:
        validators.validate_email(email)
    except ValidationError:
        return TemplateResponse(
            request,
            "zerver/invalid_email.html",
            context={"invalid_email": True},
            status=400,
        )

    if realm_creation:
        # For creating a new realm, there is no existing realm or domain
        realm = None
    else:
        assert prereg_user is not None
        assert prereg_user.realm is not None
        if get_subdomain(request) != prereg_user.realm.string_id:
            return render_confirmation_key_error(
                request, ConfirmationKeyError(ConfirmationKeyError.DOES_NOT_EXIST)
            )
        realm = prereg_user.realm
        try:
            email_allowed_for_realm(email, realm)
        except DomainNotAllowedForRealmError:
            return TemplateResponse(
                request,
                "zerver/invalid_email.html",
                context={"realm_name": realm.name, "closed_domain": True},
                status=400,
            )
        except DisposableEmailError:
            return TemplateResponse(
                request,
                "zerver/invalid_email.html",
                context={"realm_name": realm.name, "disposable_emails_not_allowed": True},
                status=400,
            )
        except EmailContainsPlusError:
            return TemplateResponse(
                request,
                "zerver/invalid_email.html",
                context={"realm_name": realm.name, "email_contains_plus": True},
                status=400,
            )

        if realm.deactivated:
            # The user is trying to register for a deactivated realm. Advise them to
            # contact support.
            return redirect_to_deactivation_notice()

        try:
            validate_email_not_already_in_realm(realm, email, allow_inactive_mirror_dummies=True)
        except ValidationError:
            return redirect_to_email_login_url(email)

        if settings.BILLING_ENABLED:
            from corporate.lib.registration import (
                check_spare_licenses_available_for_registering_new_user,
            )
            from corporate.lib.stripe import LicenseLimitError

            try:
                check_spare_licenses_available_for_registering_new_user(realm, email, role=role)
            except LicenseLimitError:
                return TemplateResponse(request, "zerver/no_spare_licenses.html")

    name_validated = False
    require_ldap_password = False

    if from_confirmation:
        with suppress(KeyError):
            del request.session["authenticated_full_name"]

        ldap_full_name = None
        if settings.POPULATE_PROFILE_VIA_LDAP:
            # If the user can be found in LDAP, we'll take the full name from the directory,
            # and further down create a form pre-filled with it.
            for backend in get_backends():
                if isinstance(backend, LDAPBackend):
                    try:
                        ldap_username = backend.django_to_ldap_username(email)
                    except NoMatchingLDAPUserError:
                        logger.warning("New account email %s could not be found in LDAP", email)
                        break

                    # Note that this `ldap_user` object is not a
                    # `ZulipLDAPUser` with a `Realm` attached, so
                    # calling `.populate_user()` on it will crash.
                    # This is OK, since we're just accessing this user
                    # to extract its name.
                    #
                    # TODO: We should potentially be accessing this
                    # user to sync its initial avatar and custom
                    # profile fields as well, if we indeed end up
                    # creating a user account through this flow,
                    # rather than waiting until `manage.py
                    # sync_ldap_user_data` runs to populate it.
                    ldap_user = _LDAPUser(backend, ldap_username)

                    try:
                        ldap_full_name = backend.get_mapped_name(ldap_user)
                    except TypeError:
                        break

                    # Check whether this is ZulipLDAPAuthBackend,
                    # which is responsible for authentication and
                    # requires that LDAP accounts enter their LDAP
                    # password to register, or ZulipLDAPUserPopulator,
                    # which just populates UserProfile fields (no auth).
                    require_ldap_password = isinstance(backend, ZulipLDAPAuthBackend)
                    break

        initial_data = {}
        if realm_creation:
            assert prereg_realm is not None
            initial_data = {
                "realm_name": prereg_realm.name,
                "realm_type": prereg_realm.org_type,
                "realm_default_language": prereg_realm.default_language,
                "realm_subdomain": prereg_realm.string_id,
            }

        if ldap_full_name:
            # We don't add "full_name" to initial here, because if the realm
            # already exists and form is complete (that is, no additional fields
            # need to be filled out by the user) we want the form to validate,
            # so they can be directly registered without having to go through
            # this interstitial.
            form = RegistrationForm(
                {"full_name": ldap_full_name},
                initial=initial_data,
                realm_creation=realm_creation,
                realm=realm,
            )
            request.session["authenticated_full_name"] = ldap_full_name
            name_validated = True
        elif prereg_user is not None and prereg_user.full_name:
            if prereg_user.full_name_validated:
                request.session["authenticated_full_name"] = prereg_user.full_name
                name_validated = True
                form = RegistrationForm(
                    {"full_name": prereg_user.full_name},
                    initial=initial_data,
                    realm_creation=realm_creation,
                    realm=realm,
                )
            else:
                initial_data["full_name"] = prereg_user.full_name
                form = RegistrationForm(
                    initial=initial_data,
                    realm_creation=realm_creation,
                    realm=realm,
                )
        elif form_full_name is not None:
            initial_data["full_name"] = form_full_name
            form = RegistrationForm(
                initial=initial_data,
                realm_creation=realm_creation,
                realm=realm,
            )
        else:
            form = RegistrationForm(
                initial=initial_data, realm_creation=realm_creation, realm=realm
            )
    else:
        postdata = request.POST.copy()
        if name_changes_disabled(realm):
            # If we populate profile information via LDAP and we have a
            # verified name from you on file, use that. Otherwise, fall
            # back to the full name in the request.
            try:
                postdata.update(full_name=request.session["authenticated_full_name"])
                name_validated = True
            except KeyError:
                pass
        form = RegistrationForm(postdata, realm_creation=realm_creation, realm=realm)

    if not (password_auth_enabled(realm) and password_required):
        form["password"].field.required = False

    if form.is_valid():
        if password_auth_enabled(realm) and form["password"].field.required:
            password = form.cleaned_data["password"]
        else:
            # If the user wasn't prompted for a password when
            # completing the authentication form (because they're
            # signing up with SSO and no password is required), set
            # the password field to `None` (Which causes Django to
            # create an unusable password).
            password = None

        if realm_creation:
            string_id = form.cleaned_data["realm_subdomain"]
            realm_name = form.cleaned_data["realm_name"]
            realm_type = form.cleaned_data["realm_type"]
            realm_default_language = form.cleaned_data["realm_default_language"]
            how_found_zulip = form.cleaned_data["how_realm_creator_found_zulip"]
            how_found_zulip_extra_context = ""
            if how_found_zulip in HOW_FOUND_ZULIP_EXTRA_CONTEXT:
                extra_context_field = HOW_FOUND_ZULIP_EXTRA_CONTEXT[how_found_zulip]
                how_found_zulip_extra_context = form.cleaned_data[extra_context_field]

            realm = do_create_realm(
                string_id,
                realm_name,
                org_type=realm_type,
                default_language=realm_default_language,
                prereg_realm=prereg_realm,
                how_realm_creator_found_zulip=RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS[
                    how_found_zulip
                ],
                how_realm_creator_found_zulip_extra_context=how_found_zulip_extra_context,
            )
        assert realm is not None

        full_name = form.cleaned_data["full_name"]
        enable_marketing_emails = form.cleaned_data["enable_marketing_emails"]
        email_address_visibility = form.cleaned_data["email_address_visibility"]
        default_stream_group_names = request.POST.getlist("default_stream_group")
        default_stream_groups = lookup_default_stream_groups(default_stream_group_names, realm)

        if source_realm_id is not None:
            # Non-integer realm_id values like "string" are treated
            # like the "Do not import" value of "".
            source_profile: UserProfile | None = get_source_profile(email, source_realm_id)
        else:
            source_profile = None

        if not realm_creation:
            try:
                existing_user_profile: UserProfile | None = get_user_by_delivery_email(email, realm)
            except UserProfile.DoesNotExist:
                existing_user_profile = None
        else:
            existing_user_profile = None

        user_profile: UserProfile | None = None
        return_data: dict[str, bool] = {}
        if ldap_auth_enabled(realm):
            # If the user was authenticated using an external SSO
            # mechanism like Google or GitHub auth, then authentication
            # will have already been done before creating the
            # PreregistrationUser object with password_required=False, and
            # so we don't need to worry about passwords.
            #
            # If instead the realm is using EmailAuthBackend, we will
            # set their password above.
            #
            # But if the realm is using LDAPAuthBackend, we need to verify
            # their LDAP password (which will, as a side effect, create
            # the user account) here using authenticate.
            # prereg_user.realm_creation carries the information about whether
            # we're in realm creation mode, and the ldap flow will handle
            # that and create the user with the appropriate parameters.
            user = authenticate(
                request=request,
                username=email,
                password=password,
                realm=realm,
                prereg_user=prereg_user,
                prereg_realm=prereg_realm,
                return_data=return_data,
            )
            if user is None:
                # This logic is security-sensitive. The user has NOT been successfully authenticated
                # with LDAP and we need to carefully decide whether they should be permitted to proceed
                # with account creation anyway or be stopped. There are three scenarios to consider:
                #
                # 1. EmailAuthBackend is enabled for the realm. That explicitly means that a user
                #    with a valid confirmation link should be able to create an account, because
                #    they were invited or organization permissions allowed sign up.
                # 2. EmailAuthBackend is disabled - that means the organization wants to be authenticating
                #    users with an external source (LDAP or one of the ExternalAuthMethods). If the user
                #    came here through one of the ExternalAuthMethods, their identity can be considered
                #    verified and account creation can proceed.
                # 3. EmailAuthBackend is disabled and the user did not come here through an ExternalAuthMethod.
                #    That means they came here by entering their email address on the registration page
                #    and clicking the confirmation link received. That means their identity needs to be
                #    verified with LDAP - and that has just failed above. Thus the account should NOT be
                #    created.
                #
                if email_auth_enabled(realm):
                    can_use_different_backend = True
                # We can identify the user came here through an ExternalAuthMethod by password_required
                # being set to False on the PreregistrationUser object.
                elif len(get_external_method_dicts(realm)) > 0 and not password_required:
                    can_use_different_backend = True
                else:
                    can_use_different_backend = False

                if settings.LDAP_APPEND_DOMAIN:
                    # In LDAP_APPEND_DOMAIN configurations, we don't allow making a non-LDAP account
                    # if the email matches the ldap domain.
                    can_use_different_backend = can_use_different_backend and (
                        not email_belongs_to_ldap(realm, email)
                    )
                if return_data.get("no_matching_ldap_user") and can_use_different_backend:
                    # If both the LDAP and Email or Social auth backends are
                    # enabled, and there's no matching user in the LDAP
                    # directory then the intent is to create a user in the
                    # realm with their email outside the LDAP organization
                    # (with e.g. a password stored in the Zulip database,
                    # not LDAP).  So we fall through and create the new
                    # account.
                    pass
                else:
                    # TODO: This probably isn't going to give a
                    # user-friendly error message, but it doesn't
