import filecmp
import os
from typing import Any, Optional
from unittest.mock import MagicMock, patch

import orjson
from django.core import mail
from django.test import override_settings
from zulip_bots.custom_exceptions import ConfigValidationError

from zerver.actions.bots import do_change_bot_owner, do_change_default_sending_stream
from zerver.actions.realm_settings import (
    do_change_realm_permission_group_setting,
    do_set_realm_user_default_setting,
)
from zerver.actions.streams import do_change_stream_permission
from zerver.actions.user_groups import check_add_user_group
from zerver.actions.users import do_change_can_create_users, do_change_user_role, do_deactivate_user
from zerver.lib.bot_config import ConfigError, get_bot_config
from zerver.lib.bot_lib import get_bot_handler
from zerver.lib.integrations import EMBEDDED_BOTS, IncomingWebhookIntegration
from zerver.lib.request import RequestNotes
from zerver.lib.test_classes import UploadSerializeMixin, ZulipTestCase
from zerver.lib.test_helpers import avatar_disk_path, get_test_image_file
from zerver.lib.utils import assert_is_not_none
from zerver.lib.webhooks.common import WebhookConfigOption
from zerver.models import RealmUserDefault, Service, Subscription, UserProfile
from zerver.models.bots import get_bot_services
from zerver.models.groups import NamedUserGroup, SystemGroups
from zerver.models.realms import get_realm
from zerver.models.streams import get_stream
from zerver.models.users import bot_owner_user_ids, get_user, is_cross_realm_bot_email


# A test validator
def _check_string(var_name: str, val: str) -> str | None:
    if val.startswith("_"):
        return f'{var_name} starts with a "_" and is hence invalid.'
    return None


test_sample_config_options = [
    IncomingWebhookIntegration(
        "stripe",
        ["financial"],
        display_name="Stripe",
        config_options=[
            WebhookConfigOption(
                name="stripe_api_key", label="Stripe API key", validator=_check_string
            )
        ],
    ),
    IncomingWebhookIntegration("helloworld", ["misc"], display_name="Hello World"),
]


class BotTest(ZulipTestCase, UploadSerializeMixin):
    def get_bot_user(self, email: str) -> UserProfile:
        realm = get_realm("zulip")
        bot = get_user(email, realm)
        return bot

    def assert_num_bots_equal(self, count: int) -> None:
        result = self.client_get("/json/bots")
        response_dict = self.assert_json_success(result)
        self.assert_length(response_dict["bots"], count)

    def create_bot(self, **extras: Any) -> dict[str, Any]:
        bot_info = {
            "full_name": "The Bot of Hamlet",
            "short_name": "hambot",
            "bot_type": "1",
        }
        bot_info.update(extras)
        result = self.client_post("/json/bots", bot_info)
        response_dict = self.assert_json_success(result)
        return response_dict

    def test_bot_domain(self) -> None:
        self.login("hamlet")
        self.create_bot()
        self.assertTrue(UserProfile.objects.filter(email="hambot-bot@zulip.testserver").exists())
        # The other cases are hard to test directly, since we don't allow creating bots from
        # the wrong subdomain, and because 'testserver.example.com' is not a valid domain for the bot's email.
        # So we just test the Realm.get_bot_domain function.
        realm = get_realm("zulip")
        self.assertEqual(realm.get_bot_domain(), "zulip.testserver")

    def deactivate_bot(self) -> None:
        email = "hambot-bot@zulip.testserver"
        result = self.client_delete(f"/json/bots/{self.get_bot_user(email).id}")
        self.assert_json_success(result)

    def test_add_bot_with_bad_username(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)

        # Invalid username
        bot_info = dict(
            full_name="My bot name",
            short_name="my\nbot",
        )
        result = self.client_post("/json/bots", bot_info)
        self.assert_json_error(result, "Bad name or username")
        self.assert_num_bots_equal(0)

        # Empty username
        bot_info = dict(
            full_name="My bot name",
            short_name="",
        )
        result = self.client_post("/json/bots", bot_info)
        self.assert_json_error(result, "Bad name or username")
        self.assert_num_bots_equal(0)

    @override_settings(FAKE_EMAIL_DOMAIN="invaliddomain", REALM_HOSTS={"zulip": "127.0.0.1"})
    def test_add_bot_with_invalid_fake_email_domain(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        bot_info = {
            "full_name": "The Bot of Hamlet",
            "short_name": "hambot",
            "bot_type": "1",
        }
        result = self.client_post("/json/bots", bot_info)

        error_message = (
            "Can't create bots until FAKE_EMAIL_DOMAIN is correctly configured.\n"
            "Please contact your server administrator."
        )
        self.assert_json_error(result, error_message)
        self.assert_num_bots_equal(0)

    def test_add_bot_with_no_name(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        bot_info = dict(
            full_name="",
            short_name="bot",
        )
        result = self.client_post("/json/bots", bot_info)
        self.assert_json_error(result, "Name must not be empty!")
        self.assert_num_bots_equal(0)

    def test_json_users_with_bots(self) -> None:
        hamlet = self.example_user("hamlet")
        self.login_user(hamlet)
        self.assert_num_bots_equal(0)

        num_bots = 3
        for i in range(num_bots):
            full_name = f"Bot {i}"
            short_name = f"bot-{i}"
            bot_info = dict(
                full_name=full_name,
                short_name=short_name,
                bot_type=1,
            )
            result = self.client_post("/json/bots", bot_info)
            self.assert_json_success(result)

        self.assert_num_bots_equal(num_bots)

        with self.assert_database_query_count(4):
            users_result = self.client_get("/json/users")

        self.assert_json_success(users_result)

    def test_add_bot(self) -> None:
        hamlet = self.example_user("hamlet")
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        with self.capture_send_event_calls(expected_num_events=4) as events:
            result = self.create_bot()
        self.assert_num_bots_equal(1)

        email = "hambot-bot@zulip.testserver"
        bot = self.get_bot_user(email)

        (event,) = (e for e in events if e["event"]["type"] == "realm_bot")

        self.assertEqual(result["user_id"], bot.id)
        self.assertEqual(
            dict(
                type="realm_bot",
                op="add",
                bot=dict(
                    email="hambot-bot@zulip.testserver",
                    user_id=bot.id,
                    bot_type=bot.bot_type,
                    full_name="The Bot of Hamlet",
                    is_active=True,
                    api_key=result["api_key"],
                    avatar_url=result["avatar_url"],
                    default_sending_stream=None,
                    default_events_register_stream=None,
                    default_all_public_streams=False,
                    services=[],
                    owner_id=hamlet.id,
                ),
            ),
            event["event"],
        )

        users_result = self.client_get("/json/users")
        members = orjson.loads(users_result.content)["members"]
        [bot_dict] = [m for m in members if m["email"] == "hambot-bot@zulip.testserver"]
        self.assertEqual(bot_dict["bot_owner_id"], self.example_user("hamlet").id)
        self.assertEqual(bot_dict["user_id"], self.get_bot_user(email).id)

    @override_settings(FAKE_EMAIL_DOMAIN="fakedomain.com", REALM_HOSTS={"zulip": "127.0.0.1"})
    def test_add_bot_with_fake_email_domain(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        self.create_bot()
        self.assert_num_bots_equal(1)

        email = "hambot-bot@fakedomain.com"
        self.get_bot_user(email)

    @override_settings(EXTERNAL_HOST="example.com")
    def test_add_bot_verify_subdomain_in_email_address(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        self.create_bot()
        self.assert_num_bots_equal(1)

        email = "hambot-bot@zulip.example.com"
        self.get_bot_user(email)

    @override_settings(
        FAKE_EMAIL_DOMAIN="fakedomain.com", REALM_HOSTS={"zulip": "zulip.example.com"}
    )
    def test_add_bot_host_used_as_domain_if_valid(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        self.create_bot()
        self.assert_num_bots_equal(1)

        email = "hambot-bot@zulip.example.com"
        self.get_bot_user(email)

    def test_add_bot_with_username_in_use(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        self.create_bot()
        self.assert_num_bots_equal(1)

        # The short_name is used in the email, which we call
        # "Username" for legacy reasons.
        bot_info = dict(
            full_name="whatever",
            short_name="hambot",
        )
        result = self.client_post("/json/bots", bot_info)
        self.assert_json_error(result, "Email is already in use.")

        dup_full_name = "The Bot of Hamlet"

        bot_info = dict(
            full_name=dup_full_name,
            short_name="whatever",
        )
        result = self.client_post("/json/bots", bot_info)
        self.assert_json_error(result, "Name is already in use.")

    def test_add_bot_with_user_avatar(self) -> None:
        email = "hambot-bot@zulip.testserver"
        realm = get_realm("zulip")
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        with get_test_image_file("img.png") as fp:
            self.create_bot(file=fp)
            profile = get_user(email, realm)
            # Make sure that avatar image that we've uploaded is same with avatar image in the server
            self.assertTrue(
                filecmp.cmp(fp.name, os.path.splitext(avatar_disk_path(profile))[0] + ".original")
            )
        self.assert_num_bots_equal(1)

        self.assertEqual(profile.avatar_source, UserProfile.AVATAR_FROM_USER)
        self.assertTrue(os.path.exists(avatar_disk_path(profile)))

    def test_add_bot_with_too_many_files(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        with get_test_image_file("img.png") as fp1, get_test_image_file("img.gif") as fp2:
            bot_info = dict(
                full_name="whatever",
                short_name="whatever",
                file1=fp1,
                file2=fp2,
            )
            result = self.client_post("/json/bots", bot_info)
        self.assert_json_error(result, "You may only upload one file at a time")
        self.assert_num_bots_equal(0)

    def test_add_bot_with_default_sending_stream(self) -> None:
        email = "hambot-bot@zulip.testserver"
        realm = get_realm("zulip")
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        result = self.create_bot(default_sending_stream="Denmark")
        self.assert_num_bots_equal(1)
        self.assertEqual(result["default_sending_stream"], "Denmark")

        profile = get_user(email, realm)
        assert profile.default_sending_stream is not None
        self.assertEqual(profile.default_sending_stream.name, "Denmark")

    def test_add_bot_with_default_sending_stream_not_subscribed(self) -> None:
        email = "hambot-bot@zulip.testserver"
        realm = get_realm("zulip")
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        result = self.create_bot(default_sending_stream="Rome")
        self.assert_num_bots_equal(1)
        self.assertEqual(result["default_sending_stream"], "Rome")

        profile = get_user(email, realm)
        assert profile.default_sending_stream is not None
        self.assertEqual(profile.default_sending_stream.name, "Rome")

    def test_add_bot_email_address_visibility(self) -> None:
        # Test that we don't mangle the email field with
        # email_address_visibility limited to admins
        user = self.example_user("hamlet")
        realm_user_default = RealmUserDefault.objects.get(realm=user.realm)
        do_set_realm_user_default_setting(
            realm_user_default,
            "email_address_visibility",
            RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_ADMINS,
            acting_user=None,
        )
        user.refresh_from_db()

        self.login_user(user)
        self.assert_num_bots_equal(0)
        with self.capture_send_event_calls(expected_num_events=4) as events:
            result = self.create_bot()
        self.assert_num_bots_equal(1)

        email = "hambot-bot@zulip.testserver"
        bot = self.get_bot_user(email)

        (event,) = (e for e in events if e["event"]["type"] == "realm_bot")
        self.assertEqual(
            dict(
                type="realm_bot",
                op="add",
                bot=dict(
                    email="hambot-bot@zulip.testserver",
                    user_id=bot.id,
                    bot_type=bot.bot_type,
                    full_name="The Bot of Hamlet",
                    is_active=True,
                    api_key=result["api_key"],
                    avatar_url=result["avatar_url"],
                    default_sending_stream=None,
                    default_events_register_stream=None,
                    default_all_public_streams=False,
                    services=[],
                    owner_id=user.id,
                ),
            ),
            event["event"],
        )

        users_result = self.client_get("/json/users")
        members = orjson.loads(users_result.content)["members"]
        [bot_dict] = [m for m in members if m["email"] == "hambot-bot@zulip.testserver"]
        self.assertEqual(bot_dict["bot_owner_id"], user.id)
        self.assertEqual(bot_dict["user_id"], self.get_bot_user(email).id)

    def test_bot_add_subscription(self) -> None:
        """
        Calling POST /json/users/me/subscriptions should successfully add
        streams, and a stream to the
        list of subscriptions and confirm the right number of events
        are generated.
        When 'principals' has a bot, no notification message event or invitation email
        is sent when add_subscriptions_backend is called in the above API call.
        """
        hamlet = self.example_user("hamlet")
        iago = self.example_user("iago")
        self.login_user(hamlet)

        # Normal user i.e. not a bot.
        request_data = {
            "principals": '["' + iago.email + '"]',
        }
        with self.capture_send_event_calls(expected_num_events=3) as events:
            result = self.subscribe_via_post(hamlet, ["Rome"], request_data)
            self.assert_json_success(result)

        msg_event = [e for e in events if e["event"]["type"] == "message"]
        self.assert_length(msg_event, 1)  # Notification message event is sent.

        # Create a bot.
        self.assert_num_bots_equal(0)
        self.create_bot()
        self.assert_num_bots_equal(1)

        # A bot
        bot_request_data = {
            "principals": '["hambot-bot@zulip.testserver"]',
        }
        with self.capture_send_event_calls(expected_num_events=2) as events_bot:
            result = self.subscribe_via_post(hamlet, ["Rome"], bot_request_data)
            self.assert_json_success(result)

        # No notification message event or invitation email is sent because of bot.
        msg_event = [e for e in events_bot if e["event"]["type"] == "message"]
        self.assert_length(msg_event, 0)
        self.assert_length(events_bot, len(events) - 1)

        # Test runner automatically redirects all sent email to a dummy 'outbox'.
        self.assert_length(mail.outbox, 0)

    def test_add_bot_with_default_sending_stream_private_allowed(self) -> None:
        self.login("hamlet")
        user_profile = self.example_user("hamlet")
        stream = get_stream("Denmark", user_profile.realm)
        self.subscribe(user_profile, stream.name)
        do_change_stream_permission(
            stream,
            invite_only=True,
            history_public_to_subscribers=False,
            is_web_public=False,
            acting_user=user_profile,
        )

        self.assert_num_bots_equal(0)
        with self.capture_send_event_calls(expected_num_events=4) as events:
            result = self.create_bot(default_sending_stream="Denmark")
        self.assert_num_bots_equal(1)
        self.assertEqual(result["default_sending_stream"], "Denmark")

        email = "hambot-bot@zulip.testserver"
        realm = get_realm("zulip")
        profile = get_user(email, realm)
        assert profile.default_sending_stream is not None
        self.assertEqual(profile.default_sending_stream.name, "Denmark")

        (event,) = (e for e in events if e["event"]["type"] == "realm_bot")
        self.assertEqual(
            dict(
                type="realm_bot",
                op="add",
                bot=dict(
                    email="hambot-bot@zulip.testserver",
                    user_id=profile.id,
                    full_name="The Bot of Hamlet",
                    bot_type=profile.bot_type,
                    is_active=True,
                    api_key=result["api_key"],
                    avatar_url=result["avatar_url"],
                    default_sending_stream="Denmark",
                    default_events_register_stream=None,
                    default_all_public_streams=False,
                    services=[],
                    owner_id=user_profile.id,
                ),
            ),
            event["event"],
        )
        self.assertEqual(event["users"], [user_profile.id])

    def test_add_bot_with_default_sending_stream_private_denied(self) -> None:
        self.login("hamlet")
        realm = self.example_user("hamlet").realm
        stream = get_stream("Denmark", realm)
        self.unsubscribe(self.example_user("hamlet"), "Denmark")
        do_change_stream_permission(
            stream,
            invite_only=True,
            history_public_to_subscribers=False,
            is_web_public=False,
            acting_user=self.example_user("hamlet"),
        )

        bot_info = {
            "full_name": "The Bot of Hamlet",
            "short_name": "hambot",
            "default_sending_stream": "Denmark",
        }
        result = self.client_post("/json/bots", bot_info)
        self.assert_json_error(result, "Invalid channel name 'Denmark'")

    def test_add_bot_with_default_events_register_stream(self) -> None:
        bot_email = "hambot-bot@zulip.testserver"
        bot_realm = get_realm("zulip")

        self.login("hamlet")
        self.assert_num_bots_equal(0)
        result = self.create_bot(default_events_register_stream="Denmark")
        self.assert_num_bots_equal(1)
        self.assertEqual(result["default_events_register_stream"], "Denmark")

        profile = get_user(bot_email, bot_realm)
        assert profile.default_events_register_stream is not None
        self.assertEqual(profile.default_events_register_stream.name, "Denmark")

    def test_add_bot_with_default_events_register_stream_private_allowed(self) -> None:
        self.login("hamlet")
        user_profile = self.example_user("hamlet")
        stream = self.subscribe(user_profile, "Denmark")
        do_change_stream_permission(
            stream,
            invite_only=True,
            history_public_to_subscribers=False,
            is_web_public=False,
            acting_user=user_profile,
        )

        self.assert_num_bots_equal(0)
        with self.capture_send_event_calls(expected_num_events=4) as events:
            result = self.create_bot(default_events_register_stream="Denmark")
        self.assert_num_bots_equal(1)
        self.assertEqual(result["default_events_register_stream"], "Denmark")

        bot_email = "hambot-bot@zulip.testserver"
        bot_realm = get_realm("zulip")
        bot_profile = get_user(bot_email, bot_realm)
        assert bot_profile.default_events_register_stream is not None
        self.assertEqual(bot_profile.default_events_register_stream.name, "Denmark")

        (event,) = (e for e in events if e["event"]["type"] == "realm_bot")
        self.assertEqual(
            dict(
                type="realm_bot",
                op="add",
                bot=dict(
                    email="hambot-bot@zulip.testserver",
                    full_name="The Bot of Hamlet",
                    user_id=bot_profile.id,
                    bot_type=bot_profile.bot_type,
                    is_active=True,
                    api_key=result["api_key"],
                    avatar_url=result["avatar_url"],
                    default_sending_stream=None,
                    default_events_register_stream="Denmark",
                    default_all_public_streams=False,
                    services=[],
                    owner_id=user_profile.id,
                ),
            ),
            event["event"],
        )
        self.assertEqual(event["users"], [user_profile.id])

    def test_add_bot_with_default_events_register_stream_private_denied(self) -> None:
        self.login("hamlet")
        realm = self.example_user("hamlet").realm
        stream = get_stream("Denmark", realm)
        self.unsubscribe(self.example_user("hamlet"), "Denmark")
        do_change_stream_permission(
            stream,
            invite_only=True,
            history_public_to_subscribers=False,
            is_web_public=False,
            acting_user=self.example_user("hamlet"),
        )

        self.assert_num_bots_equal(0)
        bot_info = {
            "full_name": "The Bot of Hamlet",
            "short_name": "hambot",
            "default_events_register_stream": "Denmark",
        }
        result = self.client_post("/json/bots", bot_info)
        self.assert_json_error(result, "Invalid channel name 'Denmark'")

    def test_add_bot_with_default_all_public_streams(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        result = self.create_bot(default_all_public_streams=orjson.dumps(True).decode())
        self.assert_num_bots_equal(1)
        self.assertTrue(result["default_all_public_streams"])

        bot_email = "hambot-bot@zulip.testserver"
        bot_realm = get_realm("zulip")
        profile = get_user(bot_email, bot_realm)
        self.assertEqual(profile.default_all_public_streams, True)

    def test_deactivate_bot(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        self.create_bot()
        self.assert_num_bots_equal(1)
        self.deactivate_bot()
        # You can deactivate the same bot twice.
        self.deactivate_bot()
        self.assert_num_bots_equal(0)

    def test_deactivate_bot_invalid_id(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        self.create_bot()
        self.assert_num_bots_equal(1)
        invalid_bot_id = 1000
        result = self.client_delete(f"/json/bots/{invalid_bot_id}")
        self.assert_json_error(result, "No such bot")
        self.assert_num_bots_equal(1)

    def test_deactivate_bot_with_no_owners(self) -> None:
        iago = self.example_user("iago")
        self.login("iago")
        self.create_bot()
        self.assert_num_bots_equal(1)

        # Set up the bot to be a private bot, as otherwise realm admins
        # are used as default owners in the absence of .bot_owner_id.
        stream = get_stream("Denmark", get_realm("zulip"))
        do_change_stream_permission(
            stream,
            invite_only=True,
            history_public_to_subscribers=False,
            is_web_public=False,
            acting_user=iago,
        )

        new_bot = assert_is_not_none(UserProfile.objects.last())
        do_change_default_sending_stream(new_bot, stream, acting_user=iago)
        new_bot.bot_owner_id = None
        new_bot.save()

        result = self.client_delete(f"/json/bots/{new_bot.id}")
        self.assert_json_success(result)
        self.assert_num_bots_equal(0)

    def test_deactivate_bot_with_owner_deactivation(self) -> None:
        user = self.example_user("hamlet")
        self.login_user(user)

        bot_info = {
            "full_name": "The Bot of Hamlet",
            "short_name": "hambot",
        }
        result = self.client_post("/json/bots", bot_info)
        self.assert_json_success(result)

        bot_info = {
            "full_name": "The Another Bot of Hamlet",
            "short_name": "hambot-another",
        }
        result = self.client_post("/json/bots", bot_info)
        self.assert_json_success(result)

        self.assertEqual(
            UserProfile.objects.filter(is_bot=True, bot_owner=user, is_active=True).count(), 2
        )

        result = self.client_delete("/json/users/me")
        self.assert_json_success(result)
        user = self.example_user("hamlet")
        self.assertFalse(user.is_active)

        self.login("iago")
        self.assertFalse(
            UserProfile.objects.filter(is_bot=True, bot_owner=user, is_active=True).exists()
        )

    def test_cannot_deactivate_other_realm_bot(self) -> None:
        user = self.mit_user("starnine")
        self.login_user(user)
        bot_info = {
            "full_name": "The Bot in zephyr",
            "short_name": "starn-bot",
            "bot_type": "1",
        }
        result = self.client_post("/json/bots", bot_info, subdomain="zephyr")
        self.assert_json_success(result)
        result = self.client_get("/json/bots", subdomain="zephyr")
        response_dict = self.assert_json_success(result)
        bot_email = response_dict["bots"][0]["username"]
        bot = get_user(bot_email, user.realm)
        self.login("iago")
        result = self.client_delete(f"/json/bots/{bot.id}")
        self.assert_json_error(result, "No such bot")

    def test_bot_deactivation_attacks(self) -> None:
        """You cannot deactivate somebody else's bot."""
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        self.create_bot()
        self.assert_num_bots_equal(1)

        # Have Othello try to deactivate both Hamlet and
        # Hamlet's bot.
        self.login("othello")

        # Cannot deactivate a user as a bot
        result = self.client_delete("/json/bots/{}".format(self.example_user("hamlet").id))
        self.assert_json_error(result, "No such bot")

        email = "hambot-bot@zulip.testserver"
        result = self.client_delete(f"/json/bots/{self.get_bot_user(email).id}")
        self.assert_json_error(result, "Insufficient permission")

        # But we don't actually deactivate the other person's bot.
        self.login("hamlet")
        self.assert_num_bots_equal(1)

        # Cannot deactivate a bot as a user
        result = self.client_delete(f"/json/users/{self.get_bot_user(email).id}")
        self.assert_json_error(result, "No such user")
        self.assert_num_bots_equal(1)

    def test_activate_bot_with_duplicate_name(self) -> None:
        self.login("iago")
        # Create a bot and then deactivate it
        original_name = "Hamlet"
        bot_info = {
            "full_name": original_name,
            "short_name": "hambot",
        }
        result = self.client_post("/json/bots", bot_info)
        self.assert_json_success(result)
        bot_email = "hambot-bot@zulip.testserver"
        bot = self.get_bot_user(bot_email)
        do_deactivate_user(bot, False, acting_user=None)
        self.assertFalse(
            UserProfile.objects.filter(is_bot=True, id=bot.id, is_active=True).exists()
        )

        # Create another bot with the same name
        bot_info2 = {
            "full_name": original_name,
            "short_name": "hambot2",
        }
        result = self.client_post("/json/bots", bot_info2)
        self.assert_json_success(result)
        result = self.client_post(f"/json/users/{bot.id}/reactivate")
        self.assert_json_error(
            result,
            'There is already an active bot named "Hamlet" in this organization. To reactivate this bot, you must rename or deactivate the other one first.',
        )

    def test_bot_permissions(self) -> None:
        self.login("hamlet")
        self.assert_num_bots_equal(0)
        self.create_bot()
        self.assert_num_bots_equal(1)

        # Have Othello try to mess with Hamlet's bots.
        self.login("othello")
        email = "hambot-bot@zulip.testserver"

        result = self.client_post(f"/json/bots/{self.get_bot_user(email).id}/api_key/regenerate")
        self.assert_json_error(result, "Insufficient permission")

        bot_info = {
            "full_name": "Fred",
        }
        result = self.client_patch(f"/json/bots/{self.get_bot_user(email).id}", bot_info)
        self.assert_json_error(result, "Insufficient permission")

    def get_bot(self) -> dict[str, Any]:
        result = self.client_get("/json/bots")
        return self.assert_json_success(result)["bots"][0]

    def test_update_api_key(self) -> None:
        self.login("hamlet")
        self.create_bot()
        bot = self.get_bot()
        old_api_key = bot["api_key"]
        email = "hambot-bot@zulip.testserver"
        result = self.client_post(f"/json/bots/{self.get_bot_user(email).id}/api_key/regenerate")
        new_api_key = self.assert_json_success(result)["api_key"]
        self.assertNotEqual(old_api_key, new_api_key)
        bot = self.get_bot()
        self.assertEqual(new_api_key, bot["api_key"])

    def test_update_api_key_for_invalid_user(self) -> None:
        self.login("hamlet")
        invalid_user_id = 1000
        result = self.client_post(f"/json/bots/{invalid_user_id}/api_key/regenerate")
        self.assert_json_error(result, "No such bot")

    def test_add_bot_with_bot_type_default(self) -> None:
        bot_email = "hambot-bot@zulip.testserver"
        bot_realm = get_realm("zulip")

        self.login("hamlet")
        self.assert_num_bots_equal(0)
        self.create_bot(bot_type=UserProfile.DEFAULT_BOT)
        self.assert_num_bots_equal(1)

        profile = get_user(bot_email, bot_realm)
        self.assertEqual(profile.bot_type, UserProfile.DEFAULT_BOT)

    def test_add_bot_with_bot_type_incoming_webhook(self) -> None:
        bot_email = "hambot-bot@zulip.testserver"
        bot_realm = get_realm("zulip")

        self.login("hamlet")
        self.assert_num_bots_equal(0)
        self.create_bot(bot_type=UserProfile.INCOMING_WEBHOOK_BOT)
        self.assert_num_bots_equal(1)

        profile = get_user(bot_email, bot_realm)
        self.assertEqual(profile.bot_type, UserProfile.INCOMING_WEBHOOK_BOT)

    def test_add_bot_with_bot_type_invalid(self) -> None:
        bot_info = {
            "full_name": "The Bot of Hamlet",
            "short_name": "hambot",
            "bot_type": 7,
        }

        self.login("hamlet")
        self.assert_num_bots_equal(0)
        result = self.client_post("/json/bots", bot_info)
        self.assert_num_bots_equal(0)
        self.assert_json_error(result, "Invalid bot type")

    def check_user_can_create_bot(
        self, user: str, bot_name: str, bot_type: int, error_msg: Optional["str"] = None
    ) -> None:
        bot_info = {
            "full_name": bot_name,
            "short_name": bot_name,
            "bot_type": bot_type,
        }
        bot_email = f"{bot_name}-bot@zulip.testserver"
        bot_realm = get_realm("zulip")
        self.login(user)
        result = self.client_post("/json/bots", bot_info)
        if error_msg is not None:
            self.assert_json_error(result, error_msg)
        else:
            self.assert_json_success(result)
            profile = get_user(bot_email, bot_realm)
            self.assertEqual(profile.bot_type, bot_type)
            self.assertEqual(profile.bot_owner, self.example_user(user))

    def test_can_create_write_only_bots_group(self) -> None:
        """
        The `can_create_write_only_bots_group` realm setting works properly.
        """
        realm = get_realm("zulip")
        hamlet = self.example_user("hamlet")
        cordelia = self.example_user("cordelia")

        administrators_system_group = NamedUserGroup.objects.get(
            name=SystemGroups.ADMINISTRATORS, realm_for_sharding=realm, is_system_group=True
        )
