import re
from collections.abc import Sequence
from datetime import datetime, timedelta
from typing import TYPE_CHECKING
from unittest.mock import patch
from urllib.parse import quote

import orjson
import time_machine
from django.conf import settings
from django.core import mail
from django.core.mail.message import EmailMultiAlternatives
from django.http import HttpRequest
from django.test import override_settings
from django.urls import reverse
from django.utils.timezone import now as timezone_now
from typing_extensions import override

from confirmation import settings as confirmation_settings
from confirmation.models import (
    Confirmation,
    ConfirmationKeyError,
    create_confirmation_link,
    get_object_from_key,
)
from corporate.lib.stripe import get_latest_seat_count
from zerver.actions.create_realm import do_change_realm_subdomain, do_create_realm
from zerver.actions.create_user import (
    do_create_user,
    process_new_human_user,
    set_up_streams_and_groups_for_new_human_user,
)
from zerver.actions.default_streams import do_add_default_stream, do_remove_default_stream
from zerver.actions.invites import (
    do_create_multiuse_invite_link,
    do_get_invites_controlled_by_user,
    do_invite_users,
    do_revoke_multi_use_invite,
    too_many_recent_realm_invites,
)
from zerver.actions.realm_settings import (
    do_change_realm_permission_group_setting,
    do_change_realm_plan_type,
    do_set_realm_property,
)
from zerver.actions.streams import do_change_stream_group_based_setting
from zerver.actions.user_groups import check_add_user_group, do_change_user_group_permission_setting
from zerver.actions.user_settings import do_change_full_name
from zerver.actions.users import change_user_is_active
from zerver.lib.create_user import create_user
from zerver.lib.default_streams import get_slim_realm_default_streams
from zerver.lib.send_email import queue_scheduled_emails
from zerver.lib.streams import ensure_stream
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import find_key_by_email
from zerver.lib.user_groups import get_direct_user_groups, is_user_in_group
from zerver.models import (
    DefaultStream,
    Message,
    MultiuseInvite,
    NamedUserGroup,
    PreregistrationUser,
    Realm,
    ScheduledEmail,
    Stream,
    UserMessage,
    UserProfile,
)
from zerver.models.groups import SystemGroups
from zerver.models.realms import get_realm
from zerver.models.streams import get_stream
from zerver.models.users import get_user_by_delivery_email
from zerver.views.invite import INVITATION_LINK_VALIDITY_MINUTES, get_invitee_emails_set
from zerver.views.registration import accounts_home

if TYPE_CHECKING:
    from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse


class StreamSetupTest(ZulipTestCase):
    def add_messages_to_stream(self, stream_name: str) -> None:
        # Make sure that add_new_user_history has some messages
        # to process, so that we get consistent query counts.
        user = self.example_user("hamlet")
        self.subscribe(user, stream_name)

        for i in range(5):
            self.send_stream_message(user, stream_name, f"test {i}")

    def create_simple_new_user(self, realm: Realm, email: str) -> UserProfile:
        # We don't need to get bogged down in all the details of creating
        # full users to test how to set up streams.
        return create_user(
            email=email,
            password=None,
            realm=realm,
            full_name="full_name",
        )

    def test_query_counts_for_new_user_using_default_streams(self) -> None:
        DefaultStream.objects.all().delete()
        realm = get_realm("zulip")

        for i in range(25):
            stream = ensure_stream(realm, f"stream{i}", acting_user=None)
            do_add_default_stream(stream)

        self.add_messages_to_stream("stream5")

        new_user = self.create_simple_new_user(realm, "alice@zulip.com")

        with self.assert_database_query_count(14):
            set_up_streams_and_groups_for_new_human_user(
                user_profile=new_user,
                prereg_user=None,
                default_stream_groups=[],
            )

    def test_query_counts_when_admin_assigns_streams(self) -> None:
        admin = self.example_user("iago")
        realm = admin.realm

        streams = [
            get_stream("Denmark", realm),
            get_stream("Rome", realm),
            get_stream("Scotland", realm),
            get_stream("Scotland", realm),
            get_stream("Verona", realm),
            get_stream("Venice", realm),
        ]

        self.add_messages_to_stream("Rome")

        new_user_email = "bob@zulip.com"

        do_invite_users(
            admin,
            [new_user_email],
            streams,
            include_realm_default_subscriptions=False,
            invite_expires_in_minutes=1000,
        )

        prereg_user = PreregistrationUser.objects.get(email=new_user_email)

        new_user = self.create_simple_new_user(realm, new_user_email)

        with self.assert_database_query_count(17):
            set_up_streams_and_groups_for_new_human_user(
                user_profile=new_user,
                prereg_user=prereg_user,
                default_stream_groups=[],
            )

    def test_query_count_when_admin_assigns_groups(self) -> None:
        admin = self.example_user("iago")
        realm = admin.realm

        hamletcharacters_group = NamedUserGroup.objects.get(
            name="hamletcharacters", realm_for_sharding=realm
        )
        test_group = check_add_user_group(realm, "test", [admin], acting_user=admin)
        user_groups = [hamletcharacters_group, test_group]

        self.add_messages_to_stream("Rome")

        new_user_email = "bob@zulip.com"

        do_invite_users(
            admin,
            [new_user_email],
            streams=[],
            user_groups=user_groups,
            include_realm_default_subscriptions=False,
            invite_expires_in_minutes=1000,
        )

        prereg_user = PreregistrationUser.objects.get(email=new_user_email)

        new_user = self.create_simple_new_user(realm, new_user_email)

        with self.assert_database_query_count(12):
            set_up_streams_and_groups_for_new_human_user(
                user_profile=new_user,
                prereg_user=prereg_user,
                default_stream_groups=[],
            )


class InviteUserBase(ZulipTestCase):
    def check_sent_emails(self, correct_recipients: list[str], clear: bool = False) -> None:
        self.assert_length(mail.outbox, len(correct_recipients))
        email_recipients = [email.recipients()[0] for email in mail.outbox]
        self.assertEqual(sorted(email_recipients), sorted(correct_recipients))
        if len(mail.outbox) == 0:
            return

        self.assertIn("Zulip", self.email_display_from(mail.outbox[0]))

        self.assertEqual(self.email_envelope_from(mail.outbox[0]), settings.NOREPLY_EMAIL_ADDRESS)
        self.assertRegex(
            self.email_display_from(mail.outbox[0]), rf" <{self.TOKENIZED_NOREPLY_REGEX}>\Z"
        )

        if clear:
            mail.outbox = []

    def invite(
        self,
        invitee_emails: str,
        stream_names: Sequence[str],
        notify_referrer_on_join: bool = True,
        invite_expires_in_minutes: int | None = INVITATION_LINK_VALIDITY_MINUTES,
        group_ids: list[int] | None = None,
        body: str = "",
        invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"],
        include_realm_default_subscriptions: bool = False,
        realm: Realm | None = None,
        welcome_message_custom_text: str | None = None,
    ) -> "TestHttpResponse":
        """
        Invites the specified users to Zulip with the specified streams.

        users should be a string containing the users to invite, comma or
            newline separated.

        streams should be a list of strings.

        group_ids should be a list of int.

        welcome_message_custom_text should be a string.
        """
        stream_ids = [self.get_stream_id(stream_name, realm=realm) for stream_name in stream_names]

        invite_expires_in: str | int | None = invite_expires_in_minutes
        if invite_expires_in is None:
            invite_expires_in = orjson.dumps(None).decode()

        payload = {
            "invitee_emails": invitee_emails,
            "invite_expires_in_minutes": invite_expires_in,
            "stream_ids": orjson.dumps(stream_ids).decode(),
            "group_ids": orjson.dumps(group_ids).decode() if group_ids else [],
            "invite_as": invite_as,
            "include_realm_default_subscriptions": orjson.dumps(
                include_realm_default_subscriptions
            ).decode(),
            "notify_referrer_on_join": orjson.dumps(notify_referrer_on_join).decode(),
        }

        if welcome_message_custom_text is not None:
            payload["welcome_message_custom_text"] = welcome_message_custom_text

        with self.captureOnCommitCallbacks(execute=True):
            return self.client_post(
                "/json/invites",
                payload,
                subdomain=realm.string_id if realm else "zulip",
            )


class InviteUserTest(InviteUserBase):
    def test_successful_invite_user(self) -> None:
        """
        A call to /json/invites with valid parameters causes an invitation
        email to be sent.
        """
        self.login("hamlet")
        invitee = "alice-test@zulip.com"
        self.assert_json_success(self.invite(invitee, ["Denmark"]))
        self.assertTrue(find_key_by_email(invitee))
        self.check_sent_emails([invitee])

    def test_newbie_restrictions(self) -> None:
        user_profile = self.example_user("hamlet")
        invitee = "alice-test@zulip.com"
        stream_name = "Denmark"

        self.login_user(user_profile)

        result = self.invite(invitee, [stream_name])
        self.assert_json_success(result)

        user_profile.date_joined = timezone_now() - timedelta(days=10)
        user_profile.save()

        with self.settings(INVITES_MIN_USER_AGE_DAYS=5):
            result = self.invite(invitee, [stream_name])
            self.assert_json_success(result)

        with self.settings(INVITES_MIN_USER_AGE_DAYS=15):
            result = self.invite(invitee, [stream_name])
            self.assert_json_error_contains(result, "Your account is too new")

    def test_invite_limits(self) -> None:
        user_profile = self.example_user("hamlet")
        realm = user_profile.realm
        self.login_user(user_profile)

        def try_invite(
            num_invitees: int,
            *,
            default_realm_max: int,
            new_realm_max: int,
            realm_max: int,
            open_realm_creation: bool = True,
            realm: Realm | None = None,
            stream_name: str = "Denmark",
        ) -> "TestHttpResponse":
            if realm is None:
                realm = get_realm("zulip")
            invitees = ",".join(
                [f"{realm.string_id}-{i:02}@zulip.com" for i in range(num_invitees)]
            )
            with self.settings(
                OPEN_REALM_CREATION=open_realm_creation,
                INVITES_DEFAULT_REALM_DAILY_MAX=default_realm_max,
                INVITES_NEW_REALM_LIMIT_DAYS=[(1, new_realm_max)],
            ):
                realm.max_invites = realm_max
                realm.save()
                return self.invite(invitees, [stream_name], realm=realm)

        # Trip the "new realm" limits
        realm.date_created = timezone_now()
        realm.save()
        result = try_invite(30, default_realm_max=50, new_realm_max=20, realm_max=40)
        self.assert_json_error_contains(result, "reached the limit")
        self.check_sent_emails([])

        # If some other realm consumes some invites, it affects our realm.  Invite 20 users in lear:
        lear_realm = get_realm("lear")
        lear_realm.date_created = timezone_now()
        lear_realm.save()
        self.login_user(self.lear_user("king"))
        result = try_invite(
            20,
            default_realm_max=50,
            new_realm_max=20,
            realm_max=40,
            realm=lear_realm,
            stream_name="general",
        )
        self.assert_json_success(result)
        self.check_sent_emails([f"lear-{i:02}@zulip.com" for i in range(20)], clear=True)

        # Which prevents inviting 1 in our realm:
        self.login_user(user_profile)
        result = try_invite(1, default_realm_max=50, new_realm_max=20, realm_max=40)
        self.assert_json_error_contains(result, "reached the limit")
        self.check_sent_emails([])

        # If our realm max is over the default realm's, we're exempt from INVITES_NEW_REALM_LIMIT_DAYS
        result = try_invite(10, default_realm_max=15, new_realm_max=5, realm_max=20)
        self.assert_json_success(result)
        self.check_sent_emails([f"zulip-{i:02}@zulip.com" for i in range(10)], clear=True)

        # We've sent 10 invites.  Trying to invite 15 people, even if
        # 10 of them are the same, still trips the limit (10 previous
        # + 15 in this submission > 20 realm max)
        result = try_invite(15, default_realm_max=15, new_realm_max=5, realm_max=20)
        self.assert_json_error_contains(result, "reached the limit")
        self.check_sent_emails([])

        # Inviting 10 more people (to the realm max of 20) works, and
        # sends emails to the same 10 users again.
        result = try_invite(10, default_realm_max=15, new_realm_max=5, realm_max=20)
        self.assert_json_success(result)
        self.check_sent_emails([f"zulip-{i:02}@zulip.com" for i in range(10)], clear=True)

        # We've sent 20 invites.  The 10 we just sent do count against
        # us if we send to them again, since we did send mail
        result = try_invite(10, default_realm_max=15, new_realm_max=5, realm_max=20)
        self.assert_json_error_contains(result, "reached the limit")
        self.check_sent_emails([])

        # We've sent 20 invites.  The realm is exempt from the new realm max
        # (INVITES_NEW_REALM_LIMIT_DAYS) if it is old enough
        realm.date_created = timezone_now() - timedelta(days=8)
        realm.save()
        result = try_invite(10, default_realm_max=50, new_realm_max=20, realm_max=40)
        self.assert_json_success(result)
        self.check_sent_emails([f"zulip-{i:02}@zulip.com" for i in range(10)], clear=True)

        # We've sent 30 invites.  None of the limits matter if open
        # realm creation is disabled.
        result = try_invite(
            10, default_realm_max=30, new_realm_max=20, realm_max=10, open_realm_creation=False
        )
        self.assert_json_success(result)
        self.check_sent_emails([f"zulip-{i:02}@zulip.com" for i in range(10)], clear=True)

        # We've sent 40 invites "today".  Fast-forward 48 hours
        # and ensure that we can invite more people
        with time_machine.travel(timezone_now() + timedelta(hours=48), tick=False):
            result = try_invite(5, default_realm_max=30, new_realm_max=20, realm_max=10)
            self.assert_json_success(result)
            self.check_sent_emails([f"zulip-{i:02}@zulip.com" for i in range(5)], clear=True)

            # We've sent 5 invites.  Ensure we can trip the fresh "today" limit for the realm
            result = try_invite(10, default_realm_max=30, new_realm_max=20, realm_max=10)
            self.assert_json_error_contains(result, "reached the limit")
            self.check_sent_emails([])

            # We've sent 5 invites.  Reset the realm to be "recently"
            # created, and ensure that we can trip the whole-server
            # limit
            realm.date_created = timezone_now() - timedelta(days=3)
            realm.save()
            result = try_invite(10, default_realm_max=50, new_realm_max=10, realm_max=40)
            self.assert_json_error_contains(result, "reached the limit")
            self.check_sent_emails([])

    @override_settings(OPEN_REALM_CREATION=True)
    def test_limited_plan_heuristics(self) -> None:
        # There additional limits only apply if OPEN_REALM_CREATION is
        # True and the plan is "limited," which is primarily only
        # relevant on Zulip Cloud.

        realm = do_create_realm("sdfoijt23489fuskdfjhksdf", "Totally Normal")
        realm.plan_type = Realm.PLAN_TYPE_LIMITED
        realm.invite_required = False
        realm.save()

        # Create a first user
        admin_user = do_create_user(
            "someone@example.com",
            "password",
            realm,
            "full name",
            role=UserProfile.ROLE_REALM_OWNER,
            realm_creation=True,
            acting_user=None,
        )

        # Inviting would work at all
        with self.assertLogs(level="INFO") as m:
            self.assertFalse(too_many_recent_realm_invites(realm, 1))
        self.assertEqual(
            m.output,
            [
                (
                    "INFO:root:sdfoijt23489fuskdfjhksdf "
                    "(!: random-realm-name,no-realm-description,no-realm-icon,realm-created-in-last-hour,only-one-user,no-messages-sent) "
                    "inviting 1 more, have 0 recent, but only 1 current users.  "
                    "Ratio 1.0, 2 allowed"
                )
            ],
        )

        # This realm is currently very suspicious, so can only invite
        # 2 users at once (2x current 1 user)
        with self.assertLogs(level="INFO") as m:
            self.assertFalse(too_many_recent_realm_invites(realm, 2))
            self.assertTrue(too_many_recent_realm_invites(realm, 3))
        self.assertEqual(
            m.output,
            [
                (
                    "INFO:root:sdfoijt23489fuskdfjhksdf "
                    "(!: random-realm-name,no-realm-description,no-realm-icon,realm-created-in-last-hour,only-one-user,no-messages-sent) "
                    "inviting 2 more, have 0 recent, but only 1 current users.  "
                    "Ratio 2.0, 2 allowed"
                ),
                (
                    "WARNING:root:sdfoijt23489fuskdfjhksdf "
                    "(!: random-realm-name,no-realm-description,no-realm-icon,realm-created-in-last-hour,only-one-user,no-messages-sent) "
                    "inviting 3 more, have 0 recent, but only 1 current users.  "
                    "Ratio 3.0, 2 allowed"
                ),
            ],
        )

        # Having another user makes it slightly less suspicious, and
        # also able to invite more in ratio with the current count of
        # users (3x current 2 users)
        self.register("other@example.com", "test", subdomain=realm.string_id)
        with self.assertLogs(level="INFO") as m:
            self.assertFalse(too_many_recent_realm_invites(realm, 6))
            self.assertTrue(too_many_recent_realm_invites(realm, 7))
        self.assertEqual(
            m.output,
            [
                (
                    "INFO:root:sdfoijt23489fuskdfjhksdf "
                    "(!: random-realm-name,no-realm-description,no-realm-icon,realm-created-in-last-hour,no-messages-sent) "
                    "inviting 6 more, have 0 recent, but only 2 current users.  "
                    "Ratio 3.0, 3 allowed"
                ),
                (
                    "WARNING:root:sdfoijt23489fuskdfjhksdf "
                    "(!: random-realm-name,no-realm-description,no-realm-icon,realm-created-in-last-hour,no-messages-sent) "
                    "inviting 7 more, have 0 recent, but only 2 current users.  "
                    "Ratio 3.5, 3 allowed"
                ),
            ],
        )

        # Remove some more warning flags
        do_change_realm_subdomain(realm, "reasonable", acting_user=None)
        realm.description = "A real place"
        realm.date_created = timezone_now() - timedelta(hours=2)
        realm.save()

        # This is now more allowable (5x current 2 users)
        with self.assertLogs(level="INFO") as m:
            self.assertFalse(too_many_recent_realm_invites(realm, 10))
            self.assertTrue(too_many_recent_realm_invites(realm, 11))
        self.assertEqual(
            m.output,
            [
                (
                    "INFO:root:reasonable "
                    "(!: no-realm-icon,no-messages-sent) "
                    "inviting 10 more, have 0 recent, but only 2 current users.  "
                    "Ratio 5.0, 5 allowed"
                ),
                (
                    "WARNING:root:reasonable "
                    "(!: no-realm-icon,no-messages-sent) "
                    "inviting 11 more, have 0 recent, but only 2 current users.  "
                    "Ratio 5.5, 5 allowed"
                ),
            ],
        )

        # If we have a different max_invites on the realm that kicks in, though
        realm.max_invites = 8
        realm.save()
        self.assertFalse(too_many_recent_realm_invites(realm, 8))
        self.assertTrue(too_many_recent_realm_invites(realm, 9))

        # And if we have a non-default max invite then that applies
        # but not the heuristics (which would limit us to 10, here)
        realm.max_invites = 12
        realm.save()
        self.assertFalse(too_many_recent_realm_invites(realm, 12))
        self.assertTrue(too_many_recent_realm_invites(realm, 13))

        # Not being a limited plan also opens us up from the
        # heuristics.  First, set us back to the default invite limit
        realm.max_invites = settings.INVITES_DEFAULT_REALM_DAILY_MAX
        realm.save()
        with self.assertLogs(level="INFO") as m:
            self.assertFalse(too_many_recent_realm_invites(realm, 10))
            self.assertTrue(too_many_recent_realm_invites(realm, 11))
        self.assertEqual(
            m.output,
            [
                (
                    "INFO:root:reasonable "
                    "(!: no-realm-icon,no-messages-sent) "
                    "inviting 10 more, have 0 recent, but only 2 current users.  "
                    "Ratio 5.0, 5 allowed"
                ),
                (
                    "WARNING:root:reasonable "
                    "(!: no-realm-icon,no-messages-sent) "
                    "inviting 11 more, have 0 recent, but only 2 current users.  "
                    "Ratio 5.5, 5 allowed"
                ),
            ],
        )
        # Become a Standard plan
        do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD, acting_user=admin_user)
        self.assertFalse(too_many_recent_realm_invites(realm, 3000))
        self.assertTrue(too_many_recent_realm_invites(realm, 3001))
        do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD_FREE, acting_user=admin_user)
        self.assertFalse(too_many_recent_realm_invites(realm, 3000))
        self.assertTrue(too_many_recent_realm_invites(realm, 3001))

    def test_invite_user_to_realm_on_manual_license_plan(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)
        _, ledger = self.subscribe_realm_to_monthly_plan_on_manual_license_management(
            user.realm, 50, 50
        )

        with self.settings(BILLING_ENABLED=True):
            result = self.invite(self.nonreg_email("alice"), ["Denmark"])
        self.assert_json_success(result)

        ledger.licenses_at_next_renewal = get_latest_seat_count(user.realm)
        ledger.save(update_fields=["licenses_at_next_renewal"])
        with self.settings(BILLING_ENABLED=True):
            result = self.invite(self.nonreg_email("bob"), ["Denmark"])
        self.assert_json_error_contains(
            result,
            "Your organization does not have enough Zulip licenses. Invitations were not sent.",
        )

        ledger.licenses_at_next_renewal = 50
        ledger.licenses = get_latest_seat_count(user.realm) + 1
        ledger.save(update_fields=["licenses", "licenses_at_next_renewal"])
        with self.settings(BILLING_ENABLED=True):
            invitee_emails = self.nonreg_email("bob") + "," + self.nonreg_email("alice")
            result = self.invite(invitee_emails, ["Denmark"])
        self.assert_json_error_contains(
            result,
            "Your organization does not have enough Zulip licenses. Invitations were not sent.",
        )

        ledger.licenses = get_latest_seat_count(user.realm)
        ledger.save(update_fields=["licenses"])
        with self.settings(BILLING_ENABLED=True):
            result = self.invite(self.nonreg_email("bob"), ["Denmark"])
        self.assert_json_error_contains(
            result,
            "Your organization does not have enough Zulip licenses. Invitations were not sent.",
        )

        with self.settings(BILLING_ENABLED=True):
            result = self.invite(
                self.nonreg_email("bob"),
                ["Denmark"],
                invite_as=PreregistrationUser.INVITE_AS["GUEST_USER"],
            )
        self.assert_json_success(result)

    def test_cross_realm_bot(self) -> None:
        inviter = self.example_user("hamlet")
        self.login_user(inviter)

        cross_realm_bot_email = "emailgateway@zulip.com"
        legit_new_email = "fred@zulip.com"
        invitee_emails = f"{cross_realm_bot_email},{legit_new_email}"

        result = self.invite(invitee_emails, ["Denmark"])
        self.assert_json_error(
            result,
            (
                "Some of those addresses are already using Zulip, so we didn't send them an"
                " invitation. We did send invitations to everyone else!"
            ),
        )

    def test_invite_mirror_dummy_user(self) -> None:
        """
        A mirror dummy account is a temporary account
        that we keep in our system if we are mirroring
        data from something like IRC.

        We want users to eventually just sign up or
        register for Zulip, in which case we will just
        fully "activate" the account.

        Here we test that you can invite a person who
        has a mirror dummy account.
        """
        inviter = self.example_user("hamlet")
        self.login_user(inviter)

        mirror_user = self.example_user("cordelia")
        mirror_user.is_mirror_dummy = True
        mirror_user.save()
        change_user_is_active(mirror_user, False)

        self.assertEqual(
            PreregistrationUser.objects.filter(email=mirror_user.email).count(),
            0,
        )

        result = self.invite(mirror_user.email, ["Denmark"])
        self.assert_json_success(result)

        prereg_user = PreregistrationUser.objects.get(email=mirror_user.email)
        assert prereg_user.referred_by is not None and inviter is not None
        self.assertEqual(
            prereg_user.referred_by.email,
            inviter.email,
        )

    def test_invite_from_now_deactivated_user(self) -> None:
        """
        While accepting an invitation from a user,
        processing for a new user account will only
        be completed if the inviter is not deactivated
        after sending the invite.
        """
        inviter = self.example_user("hamlet")
        self.login_user(inviter)
        invitee = self.nonreg_email("alice")

        result = self.invite(invitee, ["Denmark"])
        self.assert_json_success(result)

        prereg_user = PreregistrationUser.objects.get(email=invitee)
        change_user_is_active(inviter, False)
        do_create_user(
            invitee,
            "password",
            inviter.realm,
            "full name",
            prereg_user=prereg_user,
            acting_user=None,
        )

    def test_successful_invite_user_as_owner_from_owner_account(self) -> None:
        self.login("desdemona")
        invitee = self.nonreg_email("alice")
        result = self.invite(
            invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["REALM_OWNER"]
        )
        self.assert_json_success(result)
        self.assertTrue(find_key_by_email(invitee))

        self.submit_reg_form_for_user(invitee, "password")
        invitee_profile = self.nonreg_user("alice")
        self.assertTrue(invitee_profile.is_realm_owner)
        self.assertFalse(invitee_profile.is_guest)
        self.check_user_added_in_system_group(invitee_profile)

    def test_invite_user_as_owner_from_admin_account(self) -> None:
        self.login("iago")
        invitee = self.nonreg_email("alice")
        response = self.invite(
            invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["REALM_OWNER"]
        )
        self.assert_json_error(response, "Must be an organization owner")

    def test_successful_invite_user_as_admin_from_admin_account(self) -> None:
        self.login("iago")
        invitee = self.nonreg_email("alice")
        result = self.invite(
            invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["REALM_ADMIN"]
        )
        self.assert_json_success(result)
        self.assertTrue(find_key_by_email(invitee))

        self.submit_reg_form_for_user(invitee, "password")
        invitee_profile = self.nonreg_user("alice")
        self.assertTrue(invitee_profile.is_realm_admin)
        self.assertFalse(invitee_profile.is_realm_owner)
        self.assertFalse(invitee_profile.is_guest)
        self.check_user_added_in_system_group(invitee_profile)

    def test_invite_user_as_admin_from_normal_account(self) -> None:
        self.login("hamlet")
        invitee = self.nonreg_email("alice")
        response = self.invite(
            invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["REALM_ADMIN"]
        )
        self.assert_json_error(response, "Must be an organization administrator")

    def test_successful_invite_user_as_moderator_from_admin_account(self) -> None:
        self.login("iago")
        invitee = self.nonreg_email("alice")
        result = self.invite(
            invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["MODERATOR"]
        )
        self.assert_json_success(result)
        self.assertTrue(find_key_by_email(invitee))

        self.submit_reg_form_for_user(invitee, "password")
        invitee_profile = self.nonreg_user("alice")
        self.assertFalse(invitee_profile.is_realm_admin)
        self.assertTrue(invitee_profile.is_moderator)
        self.assertFalse(invitee_profile.is_guest)
        self.check_user_added_in_system_group(invitee_profile)

    def test_invite_user_as_moderator_from_normal_account(self) -> None:
        self.login("hamlet")
        invitee = self.nonreg_email("alice")
        response = self.invite(
            invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["MODERATOR"]
        )
        self.assert_json_error(response, "Must be an organization administrator")

    def test_invite_user_as_moderator_from_moderator_account(self) -> None:
        self.login("shiva")
        invitee = self.nonreg_email("alice")
        response = self.invite(
            invitee, ["Denmark"], invite_as=PreregistrationUser.INVITE_AS["MODERATOR"]
        )
        self.assert_json_error(response, "Must be an organization administrator")

    def test_invite_user_as_invalid_type(self) -> None:
        """
        Test inviting a user as invalid type of user i.e. type of invite_as
        is not in PreregistrationUser.INVITE_AS
        """
        self.login("iago")
        invitee = self.nonreg_email("alice")
        response = self.invite(invitee, ["Denmark"], invite_as=10)
        self.assert_json_error(
            response, "Invalid invite_as: Value error, Not in the list of possible values"
        )

    def test_invite_user_with_specified_user_groups_when_cannot_add_members(self) -> None:
        hamlet = self.example_user("hamlet")
        realm = hamlet.realm
        # All users except guests have permission to send invites.
        self.assertEqual(realm.can_invite_users_group.named_user_group.name, SystemGroups.MEMBERS)

        nobody_group = NamedUserGroup.objects.get(
            name=SystemGroups.NOBODY, realm_for_sharding=realm, is_system_group=True
        )
        test_group = check_add_user_group(
            realm,
            "test",
            [hamlet],
            acting_user=hamlet,
            group_settings_map={
                "can_manage_group": nobody_group,
                "can_add_members_group": nobody_group,
            },
        )
        hamletcharacters_group = NamedUserGroup.objects.get(
            name="hamletcharacters", realm_for_sharding=realm
        )

        # Initialize settings with nobody allowed to add members or manage
