# Zulip's OpenAPI-based API documentation system is documented at
#   https://zulip.readthedocs.io/en/latest/documentation/api.html
#
# This file defines the Python code examples that appears in Zulip's
# REST API documentation, and also contains a system for running the
# example code as part of the `tools/test-api` test suite.
#
# The actual documentation appears within these blocks:
#   # {code_example|start}
#   Code here
#   # {code_example|end}
#
# Whereas the surrounding code is test setup logic.

import json
import os
import sys
from collections.abc import Callable
from email.headerregistry import Address
from functools import wraps
from typing import Any, TypeVar

from typing_extensions import ParamSpec
from zulip import Client

from zerver.models.realms import get_realm
from zerver.models.users import get_user
from zerver.openapi.openapi import validate_against_openapi_schema

ZULIP_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

TEST_FUNCTIONS: dict[str, Callable[..., object]] = {}
REGISTERED_TEST_FUNCTIONS: set[str] = set()
CALLED_TEST_FUNCTIONS: set[str] = set()

ParamT = ParamSpec("ParamT")
ReturnT = TypeVar("ReturnT")


def openapi_test_function(
    endpoint: str,
) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]:
    """This decorator is used to register an OpenAPI test function with
    its endpoint. Example usage:

    @openapi_test_function("/messages/render:post")
    def ...
    """

    def wrapper(test_func: Callable[ParamT, ReturnT]) -> Callable[ParamT, ReturnT]:
        @wraps(test_func)
        def _record_calls_wrapper(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT:
            CALLED_TEST_FUNCTIONS.add(test_func.__name__)
            return test_func(*args, **kwargs)

        REGISTERED_TEST_FUNCTIONS.add(test_func.__name__)
        TEST_FUNCTIONS[endpoint] = _record_calls_wrapper

        return _record_calls_wrapper

    return wrapper


def ensure_users(ids_list: list[int], user_names: list[str]) -> None:
    # Ensure that the list of user ids (ids_list)
    # matches the users we want to refer to (user_names).
    realm = get_realm("zulip")
    user_ids = [
        get_user(Address(username=name, domain="zulip.com").addr_spec, realm).id
        for name in user_names
    ]
    assert ids_list == user_ids


def assert_success_response(response: dict[str, Any]) -> None:
    assert "result" in response
    assert response["result"] == "success"


def assert_error_response(response: dict[str, Any], code: str = "BAD_REQUEST") -> None:
    assert "result" in response
    assert response["result"] == "error"
    assert "code" in response
    assert response["code"] == code


def get_subscribed_stream_ids(client: Client) -> list[int]:
    streams = client.get_subscriptions()
    stream_ids = [stream["stream_id"] for stream in streams["subscriptions"]]
    return stream_ids


def validate_message(client: Client, message_id: int, content: Any) -> None:
    url = "messages/" + str(message_id)
    result = client.call_endpoint(
        url=url,
        method="GET",
    )
    assert result["raw_content"] == content


def set_moderation_request_channel(client: Client, channel: str | None = "core team") -> None:
    if channel is None:
        # Disable moderation request feature
        channel_id = "-1"
    else:
        channel_id = client.get_stream_id(channel)["stream_id"]

    request = dict(moderation_request_channel_id=channel_id)
    result = client.call_endpoint("/realm", method="PATCH", request=request)
    assert_success_response(result)


def get_users_messages(client: Client, user_id: int) -> list[dict[str, Any]]:
    request: dict[str, Any] = {
        "anchor": "newest",
        "num_before": 100,
        "num_after": 0,
        "narrow": [{"operator": "sender", "operand": user_id}],
    }
    result = client.get_messages(request)
    assert_success_response(result)
    return result["messages"]


@openapi_test_function("/users/me/subscriptions:post")
def add_subscriptions(client: Client) -> None:
    # {code_example|start}
    # Create and subscribe to channel "python-test".
    result = client.add_subscriptions(
        streams=[
            {
                "name": "python-test",
                "description": "Channel for testing Python",
            },
        ],
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/me/subscriptions", "post", "200")

    user_id = 25
    ensure_users([user_id], ["newbie"])
    # {code_example|start}
    # To subscribe other users to a channel, you may pass
    # the `principals` argument, like so:
    result = client.add_subscriptions(
        streams=[
            {"name": "python-test"},
        ],
        principals=[user_id],
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/me/subscriptions", "post", "200")
    assert str(user_id) in result["subscribed"]


def test_add_subscriptions_already_subscribed(client: Client) -> None:
    result = client.add_subscriptions(
        streams=[
            {"name": "python-test"},
        ],
        principals=["newbie@zulip.com"],
    )
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/me/subscriptions", "post", "200")


def test_authorization_errors_fatal(client: Client, nonadmin_client: Client) -> None:
    client.add_subscriptions(
        streams=[
            {"name": "private-channel"},
        ],
    )
    stream_id = client.get_stream_id("private-channel")["stream_id"]
    client.call_endpoint(
        f"streams/{stream_id}",
        method="PATCH",
        request={"is_private": True},
    )
    result = nonadmin_client.add_subscriptions(
        streams=[
            {"name": "private-channel"},
        ],
        authorization_errors_fatal=False,
    )
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/me/subscriptions", "post", "200")

    result = nonadmin_client.add_subscriptions(
        streams=[
            {"name": "private-channel"},
        ],
        authorization_errors_fatal=True,
    )
    assert_error_response(result)
    validate_against_openapi_schema(result, "/users/me/subscriptions", "post", "400")


@openapi_test_function("/realm/presence:get")
def get_presence(client: Client) -> None:
    # {code_example|start}
    # Get presence information of all the users in an organization.
    result = client.get_realm_presence()
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/realm/presence", "get", "200")


@openapi_test_function("/default_streams:post")
def add_default_stream(client: Client) -> None:
    client.add_subscriptions(
        streams=[
            {
                "name": "test channel",
                "description": "New channel for testing",
            },
        ],
    )
    stream_id = client.get_stream_id("test channel")["stream_id"]
    # {code_example|start}
    # Add a channel to the set of default channels for new users.
    result = client.add_default_stream(stream_id)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/default_streams", "post", "200")


@openapi_test_function("/default_streams:delete")
def remove_default_stream(client: Client) -> None:
    stream_id = client.get_stream_id("test channel")["stream_id"]
    # {code_example|start}
    # Remove a channel from the set of default channels for new users.
    request = {"stream_id": stream_id}
    result = client.call_endpoint(
        url="/default_streams",
        method="DELETE",
        request=request,
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/default_streams", "delete", "200")


@openapi_test_function("/users/{user_id_or_email}/presence:get")
def get_user_presence(client: Client) -> None:
    # {code_example|start}
    # Get presence information for "iago@zulip.com".
    result = client.get_user_presence("iago@zulip.com")
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/{user_id_or_email}/presence", "get", "200")


@openapi_test_function("/users/{user_id}/status:get")
def get_user_status(client: Client) -> None:
    user_id = 11
    ensure_users([user_id], ["iago"])
    # {code_example|start}
    # Get the status currently set by a user.
    result = client.call_endpoint(
        url=f"/users/{user_id}/status",
        method="GET",
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/{user_id}/status", "get", "200")


@openapi_test_function("/users/me/presence:post")
def update_presence(client: Client) -> None:
    # {code_example|start}
    # Update your presence.
    request = {
        "status": "active",
        "ping_only": False,
        "new_user_input": False,
        "last_update_id": -1,
    }
    result = client.update_presence(request)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/me/presence", "post", "200")


@openapi_test_function("/users:post")
def create_user(client: Client) -> None:
    # {code_example|start}
    # Create a user.
    request = {
        "email": "newbie@zulip.com",
        "password": "temp",
        "full_name": "New User",
    }
    result = client.create_user(request)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users", "post", "200")

    # Test "Email already used error".
    result = client.create_user(request)
    assert_error_response(result)
    validate_against_openapi_schema(result, "/users", "post", "400")


@openapi_test_function("/users/me/status:post")
def update_status(client: Client) -> None:
    # {code_example|start}
    # The request contains the new status and "away" boolean.
    request = {
        "status_text": "on vacation",
        "away": False,
        "emoji_name": "car",
        "emoji_code": "1f697",
        "reaction_type": "unicode_emoji",
    }
    result = client.call_endpoint(url="/users/me/status", method="POST", request=request)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/me/status", "post", "200")

    # Test "status_text is too long error".
    request = {
        "status_text": "This is a message that exceeds 60 characters, and so should throw an error.",
        "away": "false",
    }
    result = client.call_endpoint(url="/users/me/status", method="POST", request=request)
    assert_error_response(result)
    validate_against_openapi_schema(result, "/users/me/status", "post", "400")


@openapi_test_function("/users:get")
def get_members(client: Client) -> None:
    # {code_example|start}
    # Get all users in the organization.
    result = client.get_members()
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users", "get", "200")
    members = [m for m in result["members"] if m["email"] == "newbie@zulip.com"]
    assert len(members) == 1
    newbie = members[0]
    assert not newbie["is_admin"]
    assert newbie["full_name"] == "New User"

    # {code_example|start}
    # You may pass the `client_gravatar` query parameter as follows:
    result = client.get_members({"client_gravatar": False})
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users", "get", "200")
    assert result["members"][0]["avatar_url"] is not None

    # {code_example|start}
    # You may pass the `include_custom_profile_fields` query parameter as follows:
    result = client.get_members({"include_custom_profile_fields": True})
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users", "get", "200")
    for member in result["members"]:
        if member["is_bot"]:
            assert member.get("profile_data", None) is None
        else:
            assert member.get("profile_data", None) is not None
        assert member["avatar_url"] is None


@openapi_test_function("/users/{email}:get")
def get_user_by_email(client: Client) -> None:
    email = "iago@zulip.com"
    # {code_example|start}
    result = client.call_endpoint(
        url=f"/users/{email}",
        method="GET",
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/{email}", "get", "200")


@openapi_test_function("/invites:get")
def get_invitations(client: Client) -> None:
    # {code_example|start}
    # Get all invitations.
    result = client.call_endpoint(url="/invites", method="GET")
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/invites", "get", "200")


@openapi_test_function("/invites:post")
def send_invitations(client: Client) -> None:
    stream_ids = get_subscribed_stream_ids(client)[:3]
    # {code_example|start}
    # Send invitations.
    request = {
        "invitee_emails": "example@zulip.com, logan@zulip.com",
        "invite_expires_in_minutes": 60 * 24 * 10,  # 10 days
        "invite_as": 400,
        "stream_ids": stream_ids,
    }
    result = client.call_endpoint(url="/invites", method="POST", request=request)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/invites", "post", "200")


@openapi_test_function("/invites/multiuse:post")
def create_reusable_invitation_link(client: Client) -> None:
    stream_ids = get_subscribed_stream_ids(client)[:3]
    # {code_example|start}
    # Create a reusable invitation link.
    request = {
        "invite_expires_in_minutes": 60 * 24 * 10,  # 10 days
        "invite_as": 400,
        "stream_ids": stream_ids,
    }
    result = client.call_endpoint(url="/invites/multiuse", method="POST", request=request)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/invites/multiuse", "post", "200")


@openapi_test_function("/invites/{invite_id}:delete")
def revoke_email_invitation(client: Client) -> None:
    # Send email invitation.
    email = "delete-invite@zulip.com"
    request = {
        "invitee_emails": email,
        "stream_ids": [],
    }
    client.call_endpoint(url="/invites", method="POST", request=request)
    # Get invitation ID.
    invites = client.call_endpoint(url="/invites", method="GET")["invites"]
    invite = [s for s in invites if not s["is_multiuse"] and s["email"] == email]
    assert len(invite) == 1
    invite_id = invite[0]["id"]
    # {code_example|start}
    # Revoke email invitation.
    result = client.call_endpoint(url=f"/invites/{invite_id}", method="DELETE")
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/invites/{invite_id}", "delete", "200")


@openapi_test_function("/invites/multiuse/{invite_id}:delete")
def revoke_reusable_invitation_link(client: Client) -> None:
    # Create multiuse invitation link.
    invite_url = client.call_endpoint(url="/invites/multiuse", method="POST", request={})[
        "invite_link"
    ]
    # Get invitation ID.
    invites = client.call_endpoint(url="/invites", method="GET")["invites"]
    invite = [s for s in invites if s["is_multiuse"] and s["link_url"] == invite_url]
    assert len(invite) == 1
    invite_id = invite[0]["id"]
    # {code_example|start}
    # Revoke reusable invitation link.
    result = client.call_endpoint(url=f"/invites/multiuse/{invite_id}", method="DELETE")
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/invites/multiuse/{invite_id}", "delete", "200")


@openapi_test_function("/invites/{invite_id}/resend:post")
def resend_email_invitation(client: Client) -> None:
    invites = client.call_endpoint(url="/invites", method="GET")["invites"]
    email_invites = [s for s in invites if not s["is_multiuse"]]
    assert len(email_invites) > 0
    invite_id = email_invites[0]["id"]
    # {code_example|start}
    # Resend email invitation.
    result = client.call_endpoint(url=f"/invites/{invite_id}/resend", method="POST")
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/invites/{invite_id}/resend", "post", "200")


@openapi_test_function("/realm/test_welcome_bot_custom_message:post")
def test_welcome_bot_custom_message(client: Client) -> None:
    # {code_example|start}
    # Send a test welcome bot custom message with the provided text.
    request = {"welcome_message_custom_text": "Custom Welcome Message Text"}
    result = client.call_endpoint(
        "/realm/test_welcome_bot_custom_message", method="POST", request=request
    )
    # {code_example|end}
    validate_against_openapi_schema(result, "/realm/test_welcome_bot_custom_message", "post", "200")


@openapi_test_function("/users/{user_id}:get")
def get_single_user(client: Client) -> None:
    user_id = 8
    ensure_users([user_id], ["cordelia"])
    # {code_example|start}
    # Fetch details on a user given a user ID.
    result = client.get_user_by_id(user_id)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/{user_id}", "get", "200")

    # {code_example|start}
    # If you'd like data on custom profile fields, you can request them as follows:
    result = client.get_user_by_id(user_id, include_custom_profile_fields=True)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/{user_id}", "get", "200")


@openapi_test_function("/users/{user_id}:delete")
def deactivate_user(client: Client) -> None:
    user_id = 8
    ensure_users([user_id], ["cordelia"])
    # {code_example|start}
    # Deactivate a user given a user ID.
    result = client.deactivate_user_by_id(user_id)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/{user_id}", "delete", "200")


@openapi_test_function("/users/{user_id}/reactivate:post")
def reactivate_user(client: Client) -> None:
    user_id = 8
    ensure_users([user_id], ["cordelia"])
    # {code_example|start}
    # Reactivate a user given a user ID.
    result = client.reactivate_user_by_id(user_id)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/{user_id}/reactivate", "post", "200")


@openapi_test_function("/users/{user_id}:patch")
def update_user(client: Client) -> None:
    ensure_users([8, 10], ["cordelia", "hamlet"])
    user_id = 10
    # {code_example|start}
    # Change a user's full name given a user ID.
    result = client.update_user_by_id(user_id, full_name="New Name")
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/{user_id}", "patch", "200")

    user_id = 8
    # {code_example|start}
    # Change value of the custom profile field with ID 9.
    result = client.update_user_by_id(user_id, profile_data=[{"id": 9, "value": "some data"}])
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/{user_id}", "patch", "200")


@openapi_test_function("/users/{user_id}/channels:get")
def get_user_channels(client: Client) -> None:
    user_id = 7
    ensure_users([user_id], ["zoe"])
    # {code_example|start}
    # Get channel IDs a user is subscribed to.
    result = client.call_endpoint(
        url=f"/users/{user_id}/channels",
        method="GET",
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/{user_id}/channels", "get", "200")


@openapi_test_function("/users/{user_id}/subscriptions/{stream_id}:get")
def get_subscription_status(client: Client) -> None:
    user_id = 7
    ensure_users([user_id], ["zoe"])
    stream_id = client.get_subscriptions()["subscriptions"][0]["stream_id"]
    # {code_example|start}
    # Check whether a user is a subscriber to a given channel.
    result = client.call_endpoint(
        url=f"/users/{user_id}/subscriptions/{stream_id}",
        method="GET",
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(
        result, "/users/{user_id}/subscriptions/{stream_id}", "get", "200"
    )


@openapi_test_function("/realm/linkifiers:get")
def get_realm_linkifiers(client: Client) -> None:
    # {code_example|start}
    # Fetch all the linkifiers in this organization.
    result = client.call_endpoint(
        url="/realm/linkifiers",
        method="GET",
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/realm/linkifiers", "get", "200")


@openapi_test_function("/realm/linkifiers:patch")
def reorder_realm_linkifiers(client: Client) -> None:
    realm_linkifiers = client.call_endpoint(
        url="/realm/linkifiers",
        method="GET",
    )
    reordered_linkifiers = [linkifier["id"] for linkifier in realm_linkifiers["linkifiers"]][::-1]
    # {code_example|start}
    # Reorder the linkifiers in the user's organization.
    request = {"ordered_linkifier_ids": json.dumps(reordered_linkifiers)}
    result = client.call_endpoint(url="/realm/linkifiers", method="PATCH", request=request)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/realm/linkifiers", "patch", "200")


@openapi_test_function("/realm/profile_fields:get")
def get_realm_profile_fields(client: Client) -> None:
    # {code_example|start}
    # Fetch all the custom profile fields in the user's organization.
    result = client.call_endpoint(
        url="/realm/profile_fields",
        method="GET",
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/realm/profile_fields", "get", "200")


@openapi_test_function("/realm/profile_fields:patch")
def reorder_realm_profile_fields(client: Client) -> None:
    realm_profile_fields = client.call_endpoint(
        url="/realm/profile_fields",
        method="GET",
    )
    realm_profile_field_ids = [field["id"] for field in realm_profile_fields["custom_fields"]]
    reordered_profile_fields = realm_profile_field_ids[::-1]
    # {code_example|start}
    # Reorder the custom profile fields in the user's organization.
    request = {"order": json.dumps(reordered_profile_fields)}
    result = client.call_endpoint(url="/realm/profile_fields", method="PATCH", request=request)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/realm/profile_fields", "patch", "200")


@openapi_test_function("/realm/profile_fields:post")
def create_realm_profile_field(client: Client) -> None:
    # {code_example|start}
    # Create a custom profile field in the user's organization.
    request = {"name": "Phone", "hint": "Contact no.", "field_type": 1}
    result = client.call_endpoint(url="/realm/profile_fields", method="POST", request=request)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/realm/profile_fields", "post", "200")


@openapi_test_function("/realm/filters:post")
def add_realm_filter(client: Client) -> int:
    # TODO: Switch back to using client.add_realm_filter when python-zulip-api
    # begins to support url_template.

    # {code_example|start}
    # Add a filter to automatically linkify #<number> to the corresponding
    # issue in Zulip's server repository.
    request = {
        "pattern": "#(?P<id>[0-9]+)",
        "url_template": "https://github.com/zulip/zulip/issues/{id}",
    }
    result = client.call_endpoint("/realm/filters", method="POST", request=request)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/realm/filters", "post", "200")
    return result["id"]


@openapi_test_function("/realm/filters/{filter_id}:patch")
def update_realm_filter(client: Client, filter_id: int) -> None:
    # {code_example|start}
    # Update a linkifier.
    request = {
        "pattern": "#(?P<id>[0-9]+)",
        "url_template": "https://github.com/zulip/zulip/issues/{id}",
    }
    result = client.call_endpoint(
        url=f"/realm/filters/{filter_id}", method="PATCH", request=request
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/realm/filters/{filter_id}", "patch", "200")


@openapi_test_function("/realm/filters/{filter_id}:delete")
def remove_realm_filter(client: Client, filter_id: int) -> None:
    # {code_example|start}
    # Remove a linkifier.
    result = client.remove_realm_filter(filter_id)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/realm/filters/{filter_id}", "delete", "200")


@openapi_test_function("/realm/playgrounds:post")
def add_realm_playground(client: Client) -> None:
    # {code_example|start}
    # Add a code playground for Python.
    request = {
        "name": "Python playground",
        "pygments_language": "Python",
        "url_template": "https://python.example.com?code={code}",
    }
    result = client.call_endpoint(url="/realm/playgrounds", method="POST", request=request)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/realm/playgrounds", "post", "200")


@openapi_test_function("/realm/playgrounds/{playground_id}:delete")
def remove_realm_playground(client: Client) -> None:
    # {code_example|start}
    # Remove the code playground with ID 1.
    result = client.call_endpoint(url="/realm/playgrounds/1", method="DELETE")
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/realm/playgrounds/{playground_id}", "delete", "200")


@openapi_test_function("/export/realm:get")
def get_realm_exports(client: Client) -> None:
    # {code_example|start}
    # Get organization's public data exports.
    result = client.call_endpoint(url="/export/realm", method="GET")
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/export/realm", "get", "200")


@openapi_test_function("/export/realm:post")
def export_realm(client: Client) -> None:
    # {code_example|start}
    # Create a public data export of the organization.
    result = client.call_endpoint(url="/export/realm", method="POST")
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/export/realm", "post", "200")


@openapi_test_function("/export/realm/consents:get")
def get_realm_export_consents(client: Client) -> None:
    # {code_example|start}
    # Get the consents of users for their private data exports.
    result = client.call_endpoint(url="/export/realm/consents", method="GET")
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/export/realm/consents", "get", "200")


@openapi_test_function("/users/me:get")
def get_profile(client: Client) -> None:
    # {code_example|start}
    # Get the profile of the user/bot that requests this endpoint,
    # which is `client` in this case.
    result = client.get_profile()
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/me", "get", "200")


@openapi_test_function("/users/me:delete")
def deactivate_own_user(client: Client, owner_client: Client) -> None:
    user_id = client.get_profile()["user_id"]
    # {code_example|start}
    # Deactivate the account of the current user/bot.
    result = client.call_endpoint(
        url="/users/me",
        method="DELETE",
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/users/me", "delete", "200")

    # Reactivate the account to avoid polluting other tests.
    owner_client.reactivate_user_by_id(user_id)


@openapi_test_function("/get_stream_id:get")
def get_stream_id(client: Client) -> int:
    name = "python-test"
    # {code_example|start}
    # Get the ID of a given channel name.
    result = client.get_stream_id(name)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/get_stream_id", "get", "200")
    return result["stream_id"]


@openapi_test_function("/streams/{stream_id}:delete")
def archive_stream(client: Client) -> None:
    client.add_subscriptions(
        streams=[
            {
                "name": "example to archive",
            },
        ],
    )
    stream_id = client.get_stream_id("example to archive")["stream_id"]
    # {code_example|start}
    # Archive a channel, given the channel's ID.
    result = client.delete_stream(stream_id)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/streams/{stream_id}", "delete", "200")


@openapi_test_function("/streams/{stream_id}/delete_topic:post")
def delete_topic(client: Client, stream_id: int, topic: str) -> None:
    # {code_example|start}
    # Delete a topic in a channel, given the channel's ID.
    request = {
        "topic_name": topic,
    }
    result = client.call_endpoint(
        url=f"/streams/{stream_id}/delete_topic", method="POST", request=request
    )
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/streams/{stream_id}/delete_topic", "post", "200")


@openapi_test_function("/streams:get")
def get_streams(client: Client) -> None:
    # {code_example|start}
    # Get all channels that the user has access to.
    result = client.get_streams()
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/streams", "get", "200")
    streams = [s for s in result["streams"] if s["name"] == "python-test"]
    assert streams[0]["description"] == "Channel for testing Python"

    # {code_example|start}
    # You may pass in one or more of the query parameters mentioned above
    # as keyword arguments, like so:
    result = client.get_streams(include_public=False)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/streams", "get", "200")
    assert len(result["streams"]) == 7


@openapi_test_function("/streams/{stream_id}:patch")
def update_stream(client: Client, stream_id: int) -> None:
    # {code_example|start}
    # Update settings for the channel with a given ID.
    request = {
        "stream_id": stream_id,
        "is_private": True,
    }
    result = client.update_stream(request)
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/streams/{stream_id}", "patch", "200")


@openapi_test_function("/user_groups:get")
def get_user_groups(client: Client) -> int:
    # {code_example|start}
    # Get all user groups of the organization.
    result = client.get_user_groups()
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/user_groups", "get", "200")
    [hamlet_user_group] = (u for u in result["user_groups"] if u["name"] == "hamletcharacters")
    assert hamlet_user_group["description"] == "Characters of Hamlet"
    [leadership_user_group] = (u for u in result["user_groups"] if u["name"] == "leadership")
    return leadership_user_group["id"]


@openapi_test_function("/streams/{stream_id}/members:get")
def get_subscribers(client: Client) -> None:
    user_ids = [11, 25]
    ensure_users(user_ids, ["iago", "newbie"])
    # {code_example|start}
    # Get the subscribers to a channel. Note that `client.get_subscribers`
    # takes a `stream` parameter with the channel's name and not the
    # channel's ID.
    result = client.get_subscribers(stream="python-test")
    # {code_example|end}
    assert_success_response(result)
    validate_against_openapi_schema(result, "/streams/{stream_id}/members", "get", "200")
    assert result["subscribers"] == user_ids


def get_user_agent(client: Client) -> None:
    result = client.get_user_agent()
    assert result.startswith("ZulipPython/")


@openapi_test_function("/users/me/subscriptions:get")
def get_subscriptions(client: Client) -> None:
    # {code_example|start}
    # Get all channels that the user is subscribed to.
    result = client.get_subscriptions()
    # {code_example|end}
