import re
from collections.abc import Callable
from datetime import datetime

from django.http import HttpRequest, HttpResponse
from pydantic import Json
from typing_extensions import override

from zerver.decorator import log_unsupported_webhook_event, webhook_view
from zerver.lib.exceptions import UnsupportedWebhookEventTypeError
from zerver.lib.partial import partial
from zerver.lib.response import json_success
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
from zerver.lib.validator import WildValue, check_bool, check_int, check_none_or, check_string
from zerver.lib.webhooks.common import (
    OptionalUserSpecifiedTopicStr,
    check_send_webhook_message,
    default_fixture_to_headers,
    get_event_header,
    get_setup_webhook_message,
)
from zerver.lib.webhooks.git import (
    CONTENT_MESSAGE_TEMPLATE,
    TOPIC_WITH_BRANCH_TEMPLATE,
    TOPIC_WITH_PR_OR_ISSUE_INFO_TEMPLATE,
    get_commits_comment_action_message,
    get_issue_event_message,
    get_issue_labeled_or_unlabeled_event_message,
    get_issue_milestoned_or_demilestoned_event_message,
    get_pull_request_event_message,
    get_push_commits_event_message,
    get_push_tag_event_message,
    get_release_event_message,
    get_short_sha,
    is_branch_name_notifiable,
)
from zerver.models import UserProfile

fixture_to_headers = default_fixture_to_headers("HTTP_X_GITHUB_EVENT")

TOPIC_FOR_DISCUSSION = "{repo} discussion #{number}: {title}"
DISCUSSION_TEMPLATES = {
    "created": "{sender} created [discussion #{discussion_number}]({url}) in {category}:\n\n~~~ quote\n### {title}\n{body}\n~~~",
    "generic_action": "{sender} {action} [discussion #{discussion_number}{configured_title}]({url}).",
    "deleted": "{sender} {action} discussion #{discussion_number}{configured_title}.",
    "closed": "{sender} {action} [discussion #{discussion_number}{configured_title}]({url}) as {closed_reason}.",
    "locked": "{sender} {action} [discussion #{discussion_number}{configured_title}]({url}{configured_title}){locked_reason}.",
    "labeled": "{sender} added the {label} label to [discussion #{discussion_number}{configured_title}]({url}).",
    "unlabeled": "{sender} removed the {label} label from [discussion #{discussion_number}{configured_title}]({url}).",
    "category_changed": "{sender} changed the category of [discussion #{discussion_number}{configured_title}]({url}) from {old_category} to {category}.",
    "transferred": "{sender} {action} discussion #{discussion_number}{configured_title} from {repository_name} to {new_repository_name} as [discussion #{new_discussion_number}]({url}).",
    "answered": "{sender} marked [comment #{comment_id}]({answer_url}) as the answer:\n\n~~~ quote\n{answer_body}\n~~~",
    "unanswered": "{sender} marked [comment #{comment_id}]({answer_url}) as not the answer.",
    "edited_title": "{sender} edited the title of [discussion #{discussion_number}{configured_title}]({url}):\n\n~~~ quote\n### {title}\n~~~",
    "edited_body": "{sender} edited [discussion #{discussion_number}{configured_title}]({url}):\n\n~~~ quote\n{body}\n~~~",
}


class Helper:
    def __init__(
        self,
        request: HttpRequest,
        payload: WildValue,
        include_title: bool,
        include_repository_name: bool,
    ) -> None:
        self.request = request
        self.payload = payload
        self.include_title = include_title
        self.include_repository_name = include_repository_name

    def log_unsupported(self, event: str) -> None:
        summary = f"The '{event}' event isn't currently supported by the GitHub webhook; ignoring"
        log_unsupported_webhook_event(request=self.request, summary=summary)


def get_opened_or_update_pull_request_body(helper: Helper) -> str:
    payload = helper.payload
    include_title = helper.include_title
    pull_request = payload["pull_request"]
    action = payload["action"].tame(check_string)
    if action == "synchronize":
        action = "updated"
    assignee = None
    if pull_request.get("assignee"):
        assignee = pull_request["assignee"]["login"].tame(check_string)
    description = None
    changes = payload.get("changes", {})
    if "body" in changes or action == "opened":
        description = pull_request["body"].tame(check_none_or(check_string))
    target_branch = None
    base_branch = None
    if action in ("opened", "merged", "reopened"):
        target_branch = pull_request["head"]["label"].tame(check_string)
        base_branch = pull_request["base"]["label"].tame(check_string)

    return get_pull_request_event_message(
        user_name=get_sender_name(payload),
        action=action,
        url=pull_request["html_url"].tame(check_string),
        target_branch=target_branch,
        base_branch=base_branch,
        message=description,
        assignee=assignee,
        number=pull_request["number"].tame(check_int),
        title=pull_request["title"].tame(check_string) if include_title else None,
    )


def get_assigned_or_unassigned_pull_request_body(helper: Helper) -> str:
    payload = helper.payload
    include_title = helper.include_title
    pull_request = payload["pull_request"]
    assignee = payload["assignee"]["login"].tame(check_string)

    return get_pull_request_event_message(
        user_name=get_sender_name(payload),
        action=payload["action"].tame(check_string),
        url=pull_request["html_url"].tame(check_string),
        number=pull_request["number"].tame(check_int),
        title=pull_request["title"].tame(check_string) if include_title else None,
        assignee_updated=assignee,
    )


def get_closed_pull_request_body(helper: Helper) -> str:
    payload = helper.payload
    include_title = helper.include_title
    pull_request = payload["pull_request"]
    action = "merged" if pull_request["merged"].tame(check_bool) else "closed without merge"
    return get_pull_request_event_message(
        user_name=get_sender_name(payload),
        action=action,
        url=pull_request["html_url"].tame(check_string),
        number=pull_request["number"].tame(check_int),
        title=pull_request["title"].tame(check_string) if include_title else None,
    )


def get_membership_body(helper: Helper) -> str:
    payload = helper.payload
    action = payload["action"].tame(check_string)
    member = payload["member"]
    team_name = payload["team"]["name"].tame(check_string)

    return "{sender} {action} [{username}]({html_url}) {preposition} the {team_name} team.".format(
        sender=get_sender_name(payload),
        action=action,
        username=member["login"].tame(check_string),
        html_url=member["html_url"].tame(check_string),
        preposition="from" if action == "removed" else "to",
        team_name=team_name,
    )


def get_member_body(helper: Helper) -> str:
    payload = helper.payload
    return "{} {} [{}]({}) to [{}]({}).".format(
        get_sender_name(payload),
        payload["action"].tame(check_string),
        payload["member"]["login"].tame(check_string),
        payload["member"]["html_url"].tame(check_string),
        get_repository_name(payload),
        payload["repository"]["html_url"].tame(check_string),
    )


def get_issue_body(helper: Helper) -> str:
    payload = helper.payload
    include_title = helper.include_title
    action = payload["action"].tame(check_string)
    issue = payload["issue"]
    return get_issue_event_message(
        user_name=get_sender_name(payload),
        action=action,
        url=issue["html_url"].tame(check_string),
        number=issue["number"].tame(check_int),
        message=(
            None
            if action in ("assigned", "unassigned")
            else issue["body"].tame(check_none_or(check_string))
        ),
        title=issue["title"].tame(check_string) if include_title else None,
        assignee_updated=(
            payload["assignee"]["login"].tame(check_string) if "assignee" in payload else None
        ),
    )


def get_issue_comment_body(helper: Helper) -> str:
    payload = helper.payload
    include_title = helper.include_title
    comment = payload["comment"]
    issue = payload["issue"]

    return get_pull_request_event_message(
        user_name=get_sender_name(payload),
        action=get_comment_action(payload),
        url=issue["html_url"].tame(check_string),
        number=issue["number"].tame(check_int),
        message=comment["body"].tame(check_string),
        title=issue["title"].tame(check_string) if include_title else None,
        type="PR" if is_pull_request_comment_event(payload) else "issue",
    )


def get_issue_labeled_or_unlabeled_body(helper: Helper) -> str:
    payload = helper.payload
    include_title = helper.include_title
    issue = payload["issue"]

    return get_issue_labeled_or_unlabeled_event_message(
        user_name=get_sender_name(payload),
        action="added" if payload["action"].tame(check_string) == "labeled" else "removed",
        url=issue["html_url"].tame(check_string),
        number=issue["number"].tame(check_int),
        label_name=payload["label"]["name"].tame(check_string),
        user_url=get_sender_url(payload),
        title=issue["title"].tame(check_string) if include_title else None,
    )


def get_issue_milestoned_or_demilestoned_body(helper: Helper) -> str:
    payload = helper.payload
    include_title = helper.include_title
    issue = payload["issue"]

    return get_issue_milestoned_or_demilestoned_event_message(
        user_name=get_sender_name(payload),
        action="added" if payload["action"].tame(check_string) == "milestoned" else "removed",
        url=issue["html_url"].tame(check_string),
        number=issue["number"].tame(check_int),
        milestone_name=payload["milestone"]["title"].tame(check_string),
        milestone_url=payload["milestone"]["html_url"].tame(check_string),
        user_url=get_sender_url(payload),
        title=issue["title"].tame(check_string) if include_title else None,
    )


def get_fork_body(helper: Helper) -> str:
    payload = helper.payload
    forkee = payload["forkee"]
    return "{} forked [{}]({}).".format(
        get_sender_name(payload),
        forkee["name"].tame(check_string),
        forkee["html_url"].tame(check_string),
    )


def get_deployment_body(helper: Helper) -> str:
    payload = helper.payload
    return f"{get_sender_name(payload)} created new deployment."


def get_change_deployment_status_body(helper: Helper) -> str:
    payload = helper.payload
    return "Deployment changed status to {}.".format(
        payload["deployment_status"]["state"].tame(check_string),
    )


def get_create_or_delete_body(action: str, helper: Helper) -> str:
    payload = helper.payload
    ref_type = payload["ref_type"].tame(check_string)
    return "{} {} {} {}.".format(
        get_sender_name(payload),
        action,
        ref_type,
        payload["ref"].tame(check_string),
    ).rstrip()


def get_commit_comment_body(helper: Helper) -> str:
    payload = helper.payload
    comment = payload["comment"]
    comment_url = comment["html_url"].tame(check_string)
    commit_url = comment_url.split("#", 1)[0]
    action = f"[commented]({comment_url})"
    return get_commits_comment_action_message(
        get_sender_name(payload),
        action,
        commit_url,
        comment["commit_id"].tame(check_string),
        comment["body"].tame(check_string),
    )


def get_push_tags_body(helper: Helper) -> str:
    payload = helper.payload
    return get_push_tag_event_message(
        get_sender_name(payload),
        get_tag_name_from_ref(payload["ref"].tame(check_string)),
        action="pushed" if payload["created"].tame(check_bool) else "removed",
    )


def get_push_commits_body(helper: Helper) -> str:
    payload = helper.payload
    commits_data = []
    for commit in payload["commits"]:
        if commit["author"].get("username"):
            name = commit["author"]["username"].tame(check_string)
        else:
            name = commit["author"]["name"].tame(check_string)
        commits_data.append(
            {
                "name": name,
                "sha": commit["id"].tame(check_string),
                "url": commit["url"].tame(check_string),
                "message": commit["message"].tame(check_string),
            }
        )
    return get_push_commits_event_message(
        get_sender_name(payload),
        payload["compare"].tame(check_string),
        get_branch_name_from_ref(payload["ref"].tame(check_string)),
        commits_data,
        deleted=payload["deleted"].tame(check_bool),
        force_push=payload["forced"].tame(check_bool),
        repository_name=get_repository_full_name(payload)
        if helper.include_repository_name
        else None,
        repository_url=get_repository_url(payload) if helper.include_repository_name else None,
    )


class LazyContext(dict[str, str | int]):
    """Template rendering context for discussions."""

    def __init__(self, payload: WildValue, include_title: bool) -> None:
        super().__init__()
        self.payload = payload
        self.include_title = include_title
        self.template_values: dict[str, Callable[[], str | int]] = {
            "sender": lambda: get_sender_name(self.payload),
            "author": lambda: self.payload["discussion"]["user"]["login"].tame(check_string),
            "url": lambda: self.payload["discussion"]["html_url"].tame(check_string),
            "action": lambda: self.payload["action"].tame(check_string),
            "configured_title": lambda: f" {self.template_values['title']()}"
            if self.include_title
            else "",
            "category": lambda: self.payload["discussion"]["category"]["name"].tame(check_string),
            "title": lambda: self.payload["discussion"]["title"].tame(check_string),
            "body": lambda: self.payload["discussion"]["body"].tame(check_string),
            "repository_name": lambda: self.payload["repository"]["name"].tame(check_string),
            "new_repository_name": lambda: self.payload["changes"]["new_repository"]["name"].tame(
                check_string
            ),
            "discussion_number": lambda: self.payload["discussion"]["number"].tame(check_int),
            "new_discussion_number": lambda: self.payload["changes"]["new_discussion"][
                "number"
            ].tame(check_int),
            "label": lambda: self.payload["label"]["name"].tame(check_string),
            "old_category": lambda: self.payload["changes"]["category"]["from"]["name"].tame(
                check_string
            ),
            # locked_reason includes the " as " as prefix,
            # because locked_reason could be null too, in which case,
            # we drop this entire part from the message.
            "locked_reason": lambda: f" as {self.payload['discussion']['active_lock_reason'].tame(check_string)}"
            if self.payload["discussion"]["active_lock_reason"]
            else "",
            "closed_reason": lambda: self.payload["discussion"]["state_reason"].tame(check_string),
            # answer_field is used to determine which payload field to use.
            # It is either "answer" (for answered action)
            # or "old_answer" (for unanswered action)
            "answer_field": lambda: "old_answer"
            if self.payload["action"].tame(check_string) == "unanswered"
            else "answer",
            "answer_url": lambda: self.payload[self.template_values["answer_field"]()][
                "html_url"
            ].tame(check_string),
            "answer_body": lambda: self.payload[self.template_values["answer_field"]()][
                "body"
            ].tame(check_string),
            "comment_id": lambda: self.payload[self.template_values["answer_field"]()]["id"].tame(
                check_int
            ),
        }

    @override
    def __getitem__(self, key: str) -> str | int:
        return self.template_values[key]()


def get_discussion_body(helper: Helper) -> str:
    payload = helper.payload
    action = get_discussion_action(payload)
    DISCUSSION_TEMPLATE = DISCUSSION_TEMPLATES[action]
    context = LazyContext(payload, helper.include_title)
    return DISCUSSION_TEMPLATE.format_map(context)


def get_discussion_action(payload: WildValue) -> str:
    action = payload["action"].tame(check_string)
    if action in ("unlocked", "pinned", "unpinned", "reopened"):
        action = "generic_action"
    if action == "edited":
        edited_field = "body" if "body" in payload["changes"] else "title"
        action = f"edited_{edited_field}"
    return action


def get_discussion_comment_body(helper: Helper) -> str:
    payload = helper.payload
    return get_pull_request_event_message(
        user_name=get_sender_name(payload),
        action=get_comment_action(payload),
        url=payload["discussion"]["html_url"].tame(check_string),
        number=payload["discussion"]["number"].tame(check_int),
        message=payload["comment"]["body"].tame(check_string),
        title=payload["discussion"]["title"].tame(check_string) if helper.include_title else None,
        type="discussion",
    )


def get_comment_action(payload: WildValue) -> str:
    action = payload["action"].tame(check_string)
    if action == "created":
        action = "[commented]"
    else:
        action = f"{action} a [comment]"
    action += "({}) on".format(payload["comment"]["html_url"].tame(check_string))
    return action


def get_public_body(helper: Helper) -> str:
    payload = helper.payload
    return "{} made the repository [{}]({}) public.".format(
        get_sender_name(payload),
        get_repository_full_name(payload),
        payload["repository"]["html_url"].tame(check_string),
    )


def get_wiki_pages_body(helper: Helper) -> str:
    payload = helper.payload
    wiki_page_info_template = "* {action} [{title}]({url})\n"
    wiki_info = ""
    for page in payload["pages"]:
        wiki_info += wiki_page_info_template.format(
            action=page["action"].tame(check_string),
            title=page["title"].tame(check_string),
            url=page["html_url"].tame(check_string),
        )
    return f"{get_sender_name(payload)}:\n{wiki_info.rstrip()}"


def get_watch_body(helper: Helper) -> str:
    payload = helper.payload
    return "{} starred the repository [{}]({}).".format(
        get_sender_name(payload),
        get_repository_full_name(payload),
        payload["repository"]["html_url"].tame(check_string),
    )


def get_repository_body(helper: Helper) -> str:
    payload = helper.payload
    return "{} {} the repository [{}]({}).".format(
        get_sender_name(payload),
        payload["action"].tame(check_string),
        get_repository_full_name(payload),
        payload["repository"]["html_url"].tame(check_string),
    )


def get_add_team_body(helper: Helper) -> str:
    payload = helper.payload
    return "The repository [{}]({}) was added to team {}.".format(
        get_repository_full_name(payload),
        payload["repository"]["html_url"].tame(check_string),
        payload["team"]["name"].tame(check_string),
    )


def get_team_body(helper: Helper) -> str:
    payload = helper.payload
    changes = payload["changes"]
    if "description" in changes:
        actor = get_sender_name(payload)
        new_description = payload["team"]["description"].tame(check_string)
        return f"**{actor}** changed the team description to:\n\n~~~ quote\n{new_description}\n~~~"
    if "name" in changes:
        original_name = changes["name"]["from"].tame(check_string)
        new_name = payload["team"]["name"].tame(check_string)
        return f"Team `{original_name}` was renamed to `{new_name}`."
    if "privacy" in changes:
        new_visibility = payload["team"]["privacy"].tame(check_string)
        return f"Team visibility changed to `{new_visibility}`"

    missing_keys = "/".join(sorted(changes.keys()))
    helper.log_unsupported(f"team/edited (changes: {missing_keys})")

    # Do our best to give useful info to the customer--at least
    # if they know something changed, they can go to GitHub for
    # more details.  And if it's just spam, you can control that
    # from GitHub.
    return f"Team has changes to `{missing_keys}` data."


def get_release_body(helper: Helper) -> str:
    payload = helper.payload
    if payload["release"]["name"]:
        release_name = payload["release"]["name"].tame(check_string)
    else:
        release_name = payload["release"]["tag_name"].tame(check_string)
    data = {
        "user_name": get_sender_name(payload),
        "action": payload["action"].tame(check_string),
        "tagname": payload["release"]["tag_name"].tame(check_string),
        # Not every GitHub release has a "name" set; if not there, use the tag name.
        "release_name": release_name,
        "url": payload["release"]["html_url"].tame(check_string),
    }

    return get_release_event_message(**data)


def get_page_build_body(helper: Helper) -> str:
    payload = helper.payload
    build = payload["build"]
    status = build["status"].tame(check_string)
    actions = {
        "null": "has yet to be built",
        "building": "is being built",
        "errored": "has failed: {}",
        "built": "has finished building",
    }

    action = actions.get(status, f"is {status}")
    if build["error"]["message"]:
        action = action.format(
            CONTENT_MESSAGE_TEMPLATE.format(message=build["error"]["message"].tame(check_string)),
        )

    return "GitHub Pages build, triggered by {}, {}.".format(
        payload["build"]["pusher"]["login"].tame(check_string),
        action,
    )


def get_status_body(helper: Helper) -> str:
    payload = helper.payload
    if payload["target_url"]:
        status = "[{}]({})".format(
            payload["state"].tame(check_string),
            payload["target_url"].tame(check_string),
        )
    else:
        status = payload["state"].tame(check_string)
    return "[{}]({}) changed its status to {}.".format(
        get_short_sha(payload["sha"].tame(check_string)),
        payload["commit"]["html_url"].tame(check_string),
        status,
    )


def get_locked_or_unlocked_pull_request_body(helper: Helper) -> str:
    payload = helper.payload

    action = payload["action"].tame(check_string)

    message = "{sender} has locked [PR #{pr_number}]({pr_url}) as {reason} and limited conversation to collaborators."
    if action == "unlocked":
        message = "{sender} has unlocked [PR #{pr_number}]({pr_url})."
    if payload["pull_request"]["active_lock_reason"]:
        active_lock_reason = payload["pull_request"]["active_lock_reason"].tame(check_string)
    else:
        active_lock_reason = None
    return message.format(
        sender=get_sender_name(payload),
        pr_number=payload["pull_request"]["number"].tame(check_int),
        pr_url=payload["pull_request"]["html_url"].tame(check_string),
        reason=active_lock_reason,
    )


def get_pull_request_auto_merge_body(helper: Helper) -> str:
    payload = helper.payload

    action = payload["action"].tame(check_string)

    message = "{sender} has enabled auto merge for [PR #{pr_number}]({pr_url})."
    if action == "auto_merge_disabled":
        message = "{sender} has disabled auto merge for [PR #{pr_number}]({pr_url})."
    return message.format(
        sender=get_sender_name(payload),
        pr_number=payload["pull_request"]["number"].tame(check_int),
        pr_url=payload["pull_request"]["html_url"].tame(check_string),
    )


def get_pull_request_ready_for_review_body(helper: Helper) -> str:
    payload = helper.payload

    message = "**{sender}** has marked [PR #{pr_number}]({pr_url}) as ready for review."
    return message.format(
        sender=get_sender_name(payload),
        pr_number=payload["pull_request"]["number"].tame(check_int),
        pr_url=payload["pull_request"]["html_url"].tame(check_string),
    )


def get_pull_request_review_body(helper: Helper) -> str:
    payload = helper.payload
    include_title = helper.include_title
    title = "for #{} {}".format(
        payload["pull_request"]["number"].tame(check_int),
        payload["pull_request"]["title"].tame(check_string),
    )
    return get_pull_request_event_message(
        user_name=get_sender_name(payload),
        action="submitted",
        url=payload["review"]["html_url"].tame(check_string),
        type="PR review",
        title=title if include_title else None,
        message=payload["review"]["body"].tame(check_none_or(check_string)),
    )


def get_pull_request_review_request_removed_body(helper: Helper) -> str:
    payload = helper.payload

    sender = get_sender_name(payload)
    reviewer = payload["requested_reviewer"]["login"].tame(check_string)
    pr_number = payload["pull_request"]["number"].tame(check_int)
    title = payload["pull_request"]["title"].tame(check_string)
    pr_url = payload["pull_request"]["html_url"].tame(check_string)

    return f"{sender} unassigned {reviewer} from [PR #{pr_number} {title}]({pr_url})."


def get_pull_request_converted_to_draft_body(helper: Helper) -> str:
    payload = helper.payload
    return get_pull_request_event_message(
        user_name=get_sender_name(payload),
        action="converted",
        url=payload["pull_request"]["html_url"].tame(check_string),
        number=payload["pull_request"]["number"].tame(check_int),
        title=payload["pull_request"]["title"].tame(check_string),
        suffix="to a draft",
    )


def get_pull_request_labeled_or_unlabeled_body(helper: Helper) -> str:
    payload = helper.payload
    label_name = payload["label"]["name"].tame(check_string)
    action = payload["action"].tame(check_string)
    sender = get_sender_name(payload)
    pr_number = payload["pull_request"]["number"].tame(check_int)
    pr_url = payload["pull_request"]["html_url"].tame(check_string)
    preposition = "on" if action == "labeled" else "from"
    action_word = "added" if action == "labeled" else "removed"

    return f"{sender} {action_word} the label `{label_name}` {preposition} [PR #{pr_number}]({pr_url})."


def get_pull_request_milestoned_or_demilestoned_body(helper: Helper) -> str:
    payload = helper.payload
    action = payload["action"].tame(check_string)

    if action == "milestoned":
        suffix = (
            f"to the milestone `{payload['pull_request']['milestone']['title'].tame(check_string)}`"
        )
    else:
        suffix = f"from the milestone `{payload['milestone']['title'].tame(check_string)}`"

    return get_pull_request_event_message(
        user_name=get_sender_name(payload),
        action=action,
        url=payload["pull_request"]["html_url"].tame(check_string),
        number=payload["pull_request"]["number"].tame(check_int),
        suffix=suffix,
    )


def get_pull_request_enqueued_or_dequeued_body(helper: Helper) -> str:
    payload = helper.payload
    action = payload["action"].tame(check_string)
    return get_pull_request_event_message(
        user_name=get_sender_name(payload),
        action=action,
        url=payload["pull_request"]["html_url"].tame(check_string),
        number=payload["pull_request"]["number"].tame(check_int),
        title=payload["pull_request"]["title"].tame(check_string),
        suffix=("to the merge queue" if action == "enqueued" else "from the merge queue"),
    )


def get_pull_request_review_comment_body(helper: Helper) -> str:
    payload = helper.payload
    include_title = helper.include_title
    action = payload["action"].tame(check_string)
    message = None
    if action == "created":
        message = payload["comment"]["body"].tame(check_string)

    title = "on #{} {}".format(
        payload["pull_request"]["number"].tame(check_int),
        payload["pull_request"]["title"].tame(check_string),
    )

    return get_pull_request_event_message(
        user_name=get_sender_name(payload),
        action=action,
        url=payload["comment"]["html_url"].tame(check_string),
        message=message,
        type="PR review comment",
        title=title if include_title else None,
    )


def get_pull_request_review_requested_body(helper: Helper) -> str:
    payload = helper.payload
    include_title = helper.include_title

    sender = get_sender_name(payload)
    pr_number = payload["pull_request"]["number"].tame(check_int)
    pr_url = payload["pull_request"]["html_url"].tame(check_string)
    message = "**{sender}** requested {reviewers} for a review on [PR #{pr_number}]({pr_url})."
    message_with_title = (
        "**{sender}** requested {reviewers} for a review on [PR #{pr_number} {title}]({pr_url})."
    )
    body = message_with_title if include_title else message

    if "requested_reviewer" in payload:
        reviewer = payload["requested_reviewer"]
        reviewers = "[{login}]({html_url})".format(
            login=reviewer["login"].tame(check_string),
            html_url=reviewer["html_url"].tame(check_string),
        )
    else:
        team_reviewer = payload["requested_team"]
        reviewers = "[{name}]({html_url})".format(
            name=team_reviewer["name"].tame(check_string),
            html_url=team_reviewer["html_url"].tame(check_string),
        )

    return body.format(
        sender=sender,
        reviewers=reviewers,
        pr_number=pr_number,
        pr_url=pr_url,
        title=payload["pull_request"]["title"].tame(check_string) if include_title else None,
    )


def get_check_run_body(helper: Helper) -> str:
    payload = helper.payload
    template = """
Check [{name}]({html_url}) {status} ({conclusion}). ([{short_hash}]({commit_url}))
""".strip()

    kwargs = {
        "name": payload["check_run"]["name"].tame(check_string),
        "html_url": payload["check_run"]["html_url"].tame(check_string),
        "status": payload["check_run"]["status"].tame(check_string),
        "short_hash": get_short_sha(payload["check_run"]["head_sha"].tame(check_string)),
        "commit_url": "{}/commit/{}".format(
            payload["repository"]["html_url"].tame(check_string),
            payload["check_run"]["head_sha"].tame(check_string),
        ),
        "conclusion": payload["check_run"]["conclusion"].tame(check_string),
    }

    return template.format(**kwargs)


def get_star_body(helper: Helper) -> str:
    payload = helper.payload
    template = "[{user}]({user_url}) {action} the repository [{repo}]({url})."
    return template.format(
        user=get_sender_name(payload),
        user_url=get_sender_url(payload),
        action="starred" if payload["action"].tame(check_string) == "created" else "unstarred",
        repo=get_repository_full_name(payload),
        url=payload["repository"]["html_url"].tame(check_string),
    )


def get_ping_body(helper: Helper) -> str:
    payload = helper.payload
    return get_setup_webhook_message("GitHub", get_sender_name(payload))


def get_cancelled_body(helper: Helper) -> str:
    payload = helper.payload
    template = "{user_name} cancelled their {subscription} subscription."
    return template.format(
        user_name=get_sender_name(payload),
        subscription=get_subscription(payload),
    ).rstrip()


def get_created_body(helper: Helper) -> str:
    payload = helper.payload
    template = "{user_name} subscribed for {subscription}."
    return template.format(
        user_name=get_sender_name(payload),
        subscription=get_subscription(payload),
    ).rstrip()


def get_edited_body(helper: Helper) -> str:
    payload = helper.payload
    template = "{user_name} changed who can see their sponsorship from {prior_privacy_level} to {privacy_level}."
    return template.format(
        user_name=get_sender_name(payload),
        prior_privacy_level=payload["changes"]["privacy_level"]["from"].tame(check_string),
        privacy_level=payload["sponsorship"]["privacy_level"].tame(check_string),
    ).rstrip()


def get_pending_cancellation_body(helper: Helper) -> str:
    payload = helper.payload
    template = "{user_name}'s {subscription} subscription will be cancelled on {effective_date}."
    return template.format(
        user_name=get_sender_name(payload),
        subscription=get_subscription(payload),
        effective_date=get_effective_date(payload),
    ).rstrip()


def get_pending_tier_change_body(helper: Helper) -> str:
    payload = helper.payload
    template = "{user_name}'s subscription will change from {prior_subscription} to {subscription} on {effective_date}."
    return template.format(
        user_name=get_sender_name(payload),
        prior_subscription=get_prior_subscription(payload),
        subscription=get_subscription(payload),
