//! Possible error types.

use std::{io, time::Duration};

use serde::Deserialize;
use thiserror::Error;

use crate::types::ResponseParameters;

/// An error caused by sending a request to Telegram.
#[derive(Debug, Error)]
pub enum RequestError {
    /// A Telegram API error.
    #[error("A Telegram's error: {0}")]
    Api(#[source] ApiError),

    /// The group has been migrated to a supergroup with the specified
    /// identifier.
    #[error("The group has been migrated to a supergroup with ID #{0}")]
    MigrateToChatId(i64),

    /// In case of exceeding flood control, the number of seconds left to wait
    /// before the request can be repeated.
    #[error("Retry after {0:?}")]
    RetryAfter(Duration),

    /// Network error while sending a request to Telegram.
    #[error("A network error: {0}")]
    // NOTE: this variant must not be created by anything except the From impl
    Network(#[source] reqwest::Error),

    /// Error while parsing a response from Telegram.
    ///
    /// If you've received this error, please, [open an issue] with the
    /// description of the error.
    ///
    /// [open an issue]: https://github.com/teloxide/teloxide/issues/new
    #[error("An error while parsing JSON: {source} (raw: {raw:?})")]
    InvalidJson {
        #[source]
        source: serde_json::Error,
        /// The raw string JSON that couldn't been parsed
        raw: Box<str>,
    },

    /// Occurs when trying to send a file to Telegram.
    #[error("An I/O error: {0}")]
    Io(#[source] io::Error),
}

/// An error caused by downloading a file.
#[derive(Debug, Error)]
pub enum DownloadError {
    /// A network error while downloading a file from Telegram.
    #[error("A network error: {0}")]
    // NOTE: this variant must not be created by anything except the From impl
    Network(#[source] reqwest::Error),

    /// An I/O error while writing a file to destination.
    #[error("An I/O error: {0}")]
    Io(#[from] std::io::Error),
}

pub trait AsResponseParameters {
    fn response_parameters(&self) -> Option<ResponseParameters>;

    fn retry_after(&self) -> Option<Duration> {
        self.response_parameters().and_then(|rp| match rp {
            ResponseParameters::RetryAfter(n) => Some(n),
            _ => None,
        })
    }

    fn migrate_to_chat_id(&self) -> Option<i64> {
        self.response_parameters().and_then(|rp| match rp {
            ResponseParameters::MigrateToChatId(id) => Some(id),
            _ => None,
        })
    }
}

impl AsResponseParameters for crate::RequestError {
    fn response_parameters(&self) -> Option<ResponseParameters> {
        match *self {
            Self::RetryAfter(n) => Some(ResponseParameters::RetryAfter(n)),
            Self::MigrateToChatId(id) => Some(ResponseParameters::MigrateToChatId(id)),
            _ => None,
        }
    }
}

/// A kind of an API error.
#[derive(Debug, Error, Deserialize, PartialEq, Hash, Eq, Clone)]
#[serde(field_identifier)]
#[non_exhaustive]
pub enum ApiError {
    /// Occurs when the bot tries to send message to user who blocked the bot.
    #[serde(rename = "Forbidden: bot was blocked by the user")]
    #[error("Forbidden: bot was blocked by the user")]
    BotBlocked,

    /// Occurs when the bot token is incorrect.
    #[serde(rename = "Not Found")]
    #[error("Not Found")]
    NotFound,

    /// Occurs when bot tries to modify a message without modification content.
    ///
    /// May happen in methods:
    /// 1. [`EditMessageText`]
    ///
    /// [`EditMessageText`]: crate::payloads::EditMessageText
    #[serde(
        rename = "Bad Request: message is not modified: specified new message content and reply \
                  markup are exactly the same as a current content and reply markup of the message"
    )]
    #[error(
        "Bad Request: message is not modified: specified new message content and reply markup are \
         exactly the same as a current content and reply markup of the message"
    )]
    MessageNotModified,

    /// Occurs when bot tries to forward or delete a message which was deleted.
    ///
    /// May happen in methods:
    /// 1. [`ForwardMessage`]
    /// 2. [`DeleteMessage`]
    ///
    /// [`ForwardMessage`]: crate::payloads::ForwardMessage
    /// [`DeleteMessage`]: crate::payloads::DeleteMessage
    #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")]
    #[error("Bad Request: MESSAGE_ID_INVALID")]
    MessageIdInvalid,

    /// Occurs when bot tries to forward a message which does not exists.
    ///
    /// May happen in methods:
    /// 1. [`ForwardMessage`]
    ///
    /// [`ForwardMessage`]: crate::payloads::ForwardMessage
    #[serde(rename = "Bad Request: message to forward not found")]
    #[error("Bad Request: message to forward not found")]
    MessageToForwardNotFound,

    /// Occurs when bot tries to delete a message which does not exists.
    ///
    /// May happen in methods:
    /// 1. [`DeleteMessage`]
    ///
    /// [`DeleteMessage`]: crate::payloads::DeleteMessage
    #[serde(rename = "Bad Request: message to delete not found")]
    #[error("Bad Request: message to delete not found")]
    MessageToDeleteNotFound,

    /// Occurs when bot tries to send a text message without text.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Bad Request: message text is empty")]
    #[error("Bad Request: message text is empty")]
    MessageTextIsEmpty,

    /// Occurs when bot tries to edit a message after long time.
    ///
    /// May happen in methods:
    /// 1. [`EditMessageText`]
    ///
    /// [`EditMessageText`]: crate::payloads::EditMessageText
    #[serde(rename = "Bad Request: message can't be edited")]
    #[error("Bad Request: message can't be edited")]
    MessageCantBeEdited,

    /// Occurs when bot tries to delete a someone else's message in group where
    /// it does not have enough rights.
    ///
    /// May happen in methods:
    /// 1. [`DeleteMessage`]
    ///
    /// [`DeleteMessage`]: crate::payloads::DeleteMessage
    #[serde(rename = "Bad Request: message can't be deleted")]
    #[error("Bad Request: message can't be deleted")]
    MessageCantBeDeleted,

    /// Occurs when bot tries to edit a message which does not exists.
    ///
    /// May happen in methods:
    /// 1. [`EditMessageText`]
    ///
    /// [`EditMessageText`]: crate::payloads::EditMessageText
    #[serde(rename = "Bad Request: message to edit not found")]
    #[error("Bad Request: message to edit not found")]
    MessageToEditNotFound,

    /// Occurs when bot tries to reply to a message which does not exists.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Bad Request: reply message not found")]
    #[error("Bad Request: reply message not found")]
    MessageToReplyNotFound,

    /// Occurs when bot tries to
    #[serde(rename = "Bad Request: message identifier is not specified")]
    #[error("Bad Request: message identifier is not specified")]
    MessageIdentifierNotSpecified,

    /// Occurs when bot tries to send a message with text size greater then
    /// 4096 symbols.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Bad Request: message is too long")]
    #[error("Bad Request: message is too long")]
    MessageIsTooLong,

    /// Occurs when bot tries to edit a message with text size greater then
    /// 4096 symbols.
    ///
    /// May happen in methods:
    /// 1. [`EditMessageText`]
    /// 2. [`EditMessageTextInline`]
    /// 3. [`EditMessageCaption`]
    /// 4. [`EditMessageCaptionInline`]
    ///
    /// [`EditMessageText`]: crate::payloads::EditMessageText
    /// [`EditMessageTextInline`]: crate::payloads::EditMessageTextInline
    /// [`EditMessageCaption`]: crate::payloads::EditMessageCaption
    /// [`EditMessageCaptionInline`]: crate::payloads::EditMessageCaptionInline
    #[serde(rename = "Bad Request: MESSAGE_TOO_LONG")]
    #[error("Bad Request: MESSAGE_TOO_LONG")]
    EditedMessageIsTooLong,

    /// Occurs when bot tries to send media group with more than 10 items.
    ///
    /// May happen in methods:
    /// 1. [`SendMediaGroup`]
    ///
    /// [`SendMediaGroup`]: crate::payloads::SendMediaGroup
    #[serde(rename = "Bad Request: Too much messages to send as an album")]
    #[error("Bad Request: Too much messages to send as an album")]
    ToMuchMessages,

    /// Occurs when bot tries to answer an inline query with more than 50
    /// results.
    ///
    /// Consider using offsets to paginate results.
    ///
    /// May happen in methods:
    /// 1. [`AnswerInlineQuery`]
    ///
    /// [`AnswerInlineQuery`]: crate::payloads::AnswerInlineQuery
    #[serde(rename = "Bad Request: RESULTS_TOO_MUCH")]
    #[error("Bad Request: RESULTS_TOO_MUCH")]
    TooMuchInlineQueryResults,

    /// Occurs when bot tries to stop poll that has already been stopped.
    ///
    /// May happen in methods:
    /// 1. [`SendPoll`]
    ///
    /// [`SendPoll`]: crate::payloads::SendPoll
    #[serde(rename = "Bad Request: poll has already been closed")]
    #[error("Bad Request: poll has already been closed")]
    PollHasAlreadyClosed,

    /// Occurs when bot tries to send poll with less than 2 options.
    ///
    /// May happen in methods:
    /// 1. [`SendPoll`]
    ///
    /// [`SendPoll`]: crate::payloads::SendPoll
    #[serde(rename = "Bad Request: poll must have at least 2 option")]
    #[error("Bad Request: poll must have at least 2 option")]
    PollMustHaveMoreOptions,

    /// Occurs when bot tries to send poll with more than 10 options.
    ///
    /// May happen in methods:
    /// 1. [`SendPoll`]
    ///
    /// [`SendPoll`]: crate::payloads::SendPoll
    #[serde(rename = "Bad Request: poll can't have more than 10 options")]
    #[error("Bad Request: poll can't have more than 10 options")]
    PollCantHaveMoreOptions,

    /// Occurs when bot tries to send poll with empty option (without text).
    ///
    /// May happen in methods:
    /// 1. [`SendPoll`]
    ///
    /// [`SendPoll`]: crate::payloads::SendPoll
    #[serde(rename = "Bad Request: poll options must be non-empty")]
    #[error("Bad Request: poll options must be non-empty")]
    PollOptionsMustBeNonEmpty,

    /// Occurs when bot tries to send poll with empty question (without text).
    ///
    /// May happen in methods:
    /// 1. [`SendPoll`]
    ///
    /// [`SendPoll`]: crate::payloads::SendPoll
    #[serde(rename = "Bad Request: poll question must be non-empty")]
    #[error("Bad Request: poll question must be non-empty")]
    PollQuestionMustBeNonEmpty,

    /// Occurs when bot tries to send poll with total size of options more than
    /// 100 symbols.
    ///
    /// May happen in methods:
    /// 1. [`SendPoll`]
    ///
    /// [`SendPoll`]: crate::payloads::SendPoll
    #[serde(rename = "Bad Request: poll options length must not exceed 100")]
    #[error("Bad Request: poll options length must not exceed 100")]
    PollOptionsLengthTooLong,

    /// Occurs when bot tries to send poll with question size more than 255
    /// symbols.
    ///
    /// May happen in methods:
    /// 1. [`SendPoll`]
    ///
    /// [`SendPoll`]: crate::payloads::SendPoll
    #[serde(rename = "Bad Request: poll question length must not exceed 255")]
    #[error("Bad Request: poll question length must not exceed 255")]
    PollQuestionLengthTooLong,

    /// Occurs when bot tries to stop poll with message without poll.
    ///
    /// May happen in methods:
    /// 1. [`StopPoll`]
    ///
    /// [`StopPoll`]: crate::payloads::StopPoll
    #[serde(rename = "Bad Request: message with poll to stop not found")]
    #[error("Bad Request: message with poll to stop not found")]
    MessageWithPollNotFound,

    /// Occurs when bot tries to stop poll with message without poll.
    ///
    /// May happen in methods:
    /// 1. [`StopPoll`]
    ///
    /// [`StopPoll`]: crate::payloads::StopPoll
    #[serde(rename = "Bad Request: message is not a poll")]
    #[error("Bad Request: message is not a poll")]
    MessageIsNotAPoll,

    /// Occurs when bot tries to send a message to chat in which it is not a
    /// member.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Bad Request: chat not found")]
    #[error("Bad Request: chat not found")]
    ChatNotFound,

    /// Occurs when bot tries to send method with unknown user_id.
    ///
    /// May happen in methods:
    /// 1. [`getUserProfilePhotos`]
    ///
    /// [`getUserProfilePhotos`]:
    /// crate::payloads::GetUserProfilePhotos
    #[serde(rename = "Bad Request: user not found")]
    #[error("Bad Request: user not found")]
    UserNotFound,

    /// Occurs when bot tries to send [`SetChatDescription`] with same text as
    /// in the current description.
    ///
    /// May happen in methods:
    /// 1. [`SetChatDescription`]
    ///
    /// [`SetChatDescription`]: crate::payloads::SetChatDescription
    #[serde(rename = "Bad Request: chat description is not modified")]
    #[error("Bad Request: chat description is not modified")]
    ChatDescriptionIsNotModified,

    /// Occurs when bot tries to answer to query after timeout expire.
    ///
    /// May happen in methods:
    /// 1. [`AnswerCallbackQuery`]
    ///
    /// [`AnswerCallbackQuery`]: crate::payloads::AnswerCallbackQuery
    #[serde(
        rename = "Bad Request: query is too old and response timeout expired or query id is \
                  invalid"
    )]
    #[error("Bad Request: query is too old and response timeout expired or query id is invalid")]
    InvalidQueryId,

    /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button
    /// url.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Bad Request: BUTTON_URL_INVALID")]
    #[error("Bad Request: BUTTON_URL_INVALID")]
    ButtonUrlInvalid,

    /// Occurs when bot tries to send button with data size more than 64 bytes.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")]
    #[error("Bad Request: BUTTON_DATA_INVALID")]
    ButtonDataInvalid,

    /// Occurs when bot tries to send button with data size == 0.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(
        rename = "Bad Request: can't parse inline keyboard button: Text buttons are unallowed in \
                  the inline keyboard"
    )]
    #[error(
        "Bad Request: can't parse inline keyboard button: Text buttons are unallowed in the \
         inline keyboard"
    )]
    TextButtonsAreUnallowed,

    /// Occurs when bot tries to get file by wrong file id.
    ///
    /// May happen in methods:
    /// 1. [`GetFile`]
    ///
    /// [`GetFile`]: crate::payloads::GetFile
    #[serde(rename = "Bad Request: wrong file id")]
    #[error("Bad Request: wrong file id")]
    WrongFileId,

    /// Occurs when bot tries to send files with wrong file identifier or HTTP
    /// url
    #[serde(rename = "Bad Request: wrong file identifier/HTTP URL specified")]
    #[error("Bad Request: wrong file identifier/HTTP URL specified")]
    WrongFileIdOrUrl,

    /// Occurs when When sending files with an url to a site that doesn't
    /// respond.
    #[serde(rename = "Bad Request: failed to get HTTP URL content")]
    #[error("Bad Request: failed to get HTTP URL content")]
    FailedToGetUrlContent,

    /// Occurs when bot tries to do some with group which was deactivated.
    #[serde(rename = "Bad Request: group is deactivated")]
    #[error("Bad Request: group is deactivated")]
    GroupDeactivated,

    /// Occurs when bot tries to set chat photo from file ID
    ///
    /// May happen in methods:
    /// 1. [`SetChatPhoto`]
    ///
    /// [`SetChatPhoto`]: crate::payloads::SetChatPhoto
    #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")]
    #[error("Bad Request: Photo should be uploaded as an InputFile")]
    PhotoAsInputFileRequired,

    /// Occurs when bot tries to add sticker to stickerset by invalid name.
    ///
    /// May happen in methods:
    /// 1. [`AddStickerToSet`]
    ///
    /// [`AddStickerToSet`]: crate::payloads::AddStickerToSet
    #[serde(rename = "Bad Request: STICKERSET_INVALID")]
    #[error("Bad Request: STICKERSET_INVALID")]
    InvalidStickersSet,

    /// Occurs when bot tries to create a sticker set with a name that is
    /// already used by another sticker set.
    ///
    /// May happen in methods:
    /// 1. [`CreateNewStickerSet`]
    ///
    /// [`CreateNewStickerSet`]: crate::payloads::CreateNewStickerSet
    #[serde(rename = "Bad Request: sticker set name is already occupied")]
    #[error("Bad Request: sticker set name is already occupied")]
    StickerSetNameOccupied,

    /// Occurs when bot tries to create a sticker set with user id of a bot.
    ///
    /// May happen in methods:
    /// 1. [`CreateNewStickerSet`]
    ///
    /// [`CreateNewStickerSet`]: crate::payloads::CreateNewStickerSet
    #[serde(rename = "Bad Request: USER_IS_BOT")]
    #[error("Bad Request: USER_IS_BOT")]
    StickerSetOwnerIsBot,

    /// Occurs when bot tries to create a sticker set with invalid name.
    ///
    /// From documentation of [`CreateNewStickerSet`]:
    /// > Short name of sticker set, to be used in `t.me/addstickers/` URLs
    /// (e.g., _animals_). Can contain only english letters, digits and
    /// underscores. Must begin with a letter, can't contain consecutive
    /// underscores and must end in “\_by\_<bot\_username>”. <bot\_username>
    /// is case insensitive. 1-64 characters.
    ///
    /// May happen in methods:
    /// 1. [`CreateNewStickerSet`]
    ///
    /// [`CreateNewStickerSet`]: crate::payloads::CreateNewStickerSet
    #[serde(rename = "Bad Request: invalid sticker set name is specified")]
    #[error("Bad Request: invalid sticker set name is specified")]
    InvalidStickerName,

    /// Occurs when bot tries to pin a message without rights to pin in this
    /// chat.
    ///
    /// May happen in methods:
    /// 1. [`PinChatMessage`]
    ///
    /// [`PinChatMessage`]: crate::payloads::PinChatMessage
    #[serde(rename = "Bad Request: not enough rights to pin a message")]
    #[error("Bad Request: not enough rights to pin a message")]
    NotEnoughRightsToPinMessage,

    /// Occurs when bot tries to pin or unpin a message without rights to pin
    /// in this chat.
    ///
    /// May happen in methods:
    /// 1. [`PinChatMessage`]
    /// 2. [`UnpinChatMessage`]
    ///
    /// [`PinChatMessage`]: crate::payloads::PinChatMessage
    /// [`UnpinChatMessage`]: crate::payloads::UnpinChatMessage
    #[serde(rename = "Bad Request: not enough rights to manage pinned messages in the chat")]
    #[error("Bad Request: not enough rights to manage pinned messages in the chat")]
    NotEnoughRightsToManagePins,

    /// Occurs when bot tries change default chat permissions without "Ban
    /// Users" permission in this chat.
    ///
    /// May happen in methods:
    /// 1. [`SetChatPermissions`]
    ///
    /// [`SetChatPermissions`]: crate::payloads::SetChatPermissions
    #[serde(rename = "Bad Request: not enough rights to change chat permissions")]
    #[error("Bad Request: not enough rights to change chat permissions")]
    NotEnoughRightsToChangeChatPermissions,

    /// Occurs when bot tries to use method in group which is allowed only in a
    /// supergroup or channel.
    #[serde(rename = "Bad Request: method is available only for supergroups and channel")]
    #[error("Bad Request: method is available only for supergroups and channel")]
    MethodNotAvailableInPrivateChats,

    /// Occurs when bot tries to demote chat creator.
    ///
    /// May happen in methods:
    /// 1. [`PromoteChatMember`]
    ///
    /// [`PromoteChatMember`]: crate::payloads::PromoteChatMember
    #[serde(rename = "Bad Request: can't demote chat creator")]
    #[error("Bad Request: can't demote chat creator")]
    CantDemoteChatCreator,

    /// Occurs when bot tries to restrict self in group chats.
    ///
    /// May happen in methods:
    /// 1. [`RestrictChatMember`]
    ///
    /// [`RestrictChatMember`]: crate::payloads::RestrictChatMember
    #[serde(rename = "Bad Request: can't restrict self")]
    #[error("Bad Request: can't restrict self")]
    CantRestrictSelf,

    /// Occurs when bot tries to restrict chat member without rights to
    /// restrict in this chat.
    ///
    /// May happen in methods:
    /// 1. [`RestrictChatMember`]
    ///
    /// [`RestrictChatMember`]: crate::payloads::RestrictChatMember
    #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")]
    #[error("Bad Request: not enough rights to restrict/unrestrict chat member")]
    NotEnoughRightsToRestrict,

    /// Occurs when bot tries to post a message in a channel without "Post
    /// Messages" admin right.
    #[serde(rename = "Bad Request: need administrator rights in the channel chat")]
    #[error("Bad Request: need administrator rights in the channel chat")]
    NotEnoughRightsToPostMessages,

    /// Occurs when bot tries set webhook to protocol other than HTTPS.
    ///
    /// May happen in methods:
    /// 1. [`SetWebhook`]
    ///
    /// [`SetWebhook`]: crate::payloads::SetWebhook
    #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")]
    #[error("Bad Request: bad webhook: HTTPS url must be provided for webhook")]
    WebhookRequireHttps,

    /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or
    /// 8443.
    ///
    /// May happen in methods:
    /// 1. [`SetWebhook`]
    ///
    /// [`SetWebhook`]: crate::payloads::SetWebhook
    #[serde(
        rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 or \
                  8443"
    )]
    #[error("Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 or 8443")]
    BadWebhookPort,

    /// Occurs when bot tries to set webhook to unknown host.
    ///
    /// May happen in methods:
    /// 1. [`SetWebhook`]
    ///
    /// [`SetWebhook`]: crate::payloads::SetWebhook
    #[serde(
        rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known"
    )]
    #[error("Bad Request: bad webhook: Failed to resolve host: Name or service not known")]
    UnknownHost,

    /// Occurs when bot tries to set webhook to invalid URL.
    ///
    /// May happen in methods:
    /// 1. [`SetWebhook`]
    ///
    /// [`SetWebhook`]: crate::payloads::SetWebhook
    #[serde(rename = "Bad Request: can't parse URL")]
    #[error("Bad Request: can't parse URL")]
    CantParseUrl,

    /// Occurs when bot tries to send message with unfinished entities.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Bad Request: can't parse entities")]
    #[error("Bad Request: can't parse entities")]
    CantParseEntities,

    /// Occurs when bot tries to use getUpdates while webhook is active.
    ///
    /// May happen in methods:
    /// 1. [`GetUpdates`]
    ///
    /// [`GetUpdates`]: crate::payloads::GetUpdates
    #[serde(rename = "can't use getUpdates method while webhook is active")]
    #[error("can't use getUpdates method while webhook is active")]
    CantGetUpdates,

    /// Occurs when bot tries to do some in group where bot was kicked.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Unauthorized: bot was kicked from a chat")]
    #[error("Unauthorized: bot was kicked from a chat")]
    BotKicked,

    /// Occurs when bot tries to do something in a supergroup the bot was
    /// kicked from.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Forbidden: bot was kicked from the supergroup chat")]
    #[error("Forbidden: bot was kicked from the supergroup chat")]
    BotKickedFromSupergroup,

    /// Occurs when bot tries to send a message to a deactivated user (i.e. a
    /// user that was banned by telegram).
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Forbidden: user is deactivated")]
    #[error("Forbidden: user is deactivated")]
    UserDeactivated,

    /// Occurs when you tries to initiate conversation with a user.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")]
    #[error("Unauthorized: bot can't initiate conversation with a user")]
    CantInitiateConversation,

    /// Occurs when you tries to send message to bot.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Unauthorized: bot can't send messages to bots")]
    #[error("Unauthorized: bot can't send messages to bots")]
    CantTalkWithBots,

    /// Occurs when bot tries to send button with invalid http url.
    ///
    /// May happen in methods:
    /// 1. [`SendMessage`]
    ///
    /// [`SendMessage`]: crate::payloads::SendMessage
    #[serde(rename = "Bad Request: wrong HTTP URL")]
    #[error("Bad Request: wrong HTTP URL")]
    WrongHttpUrl,

    /// Occurs when bot tries GetUpdate before the timeout. Make sure that only
    /// one Updater is running.
    ///
    /// May happen in methods:
    /// 1. [`GetUpdates`]
    ///
    /// [`GetUpdates`]: crate::payloads::GetUpdates
    #[serde(
        rename = "Conflict: terminated by other getUpdates request; make sure that only one bot \
                  instance is running"
    )]
    #[error(
        "Conflict: terminated by other getUpdates request; make sure that only one bot instance \
         is running"
    )]
    TerminatedByOtherGetUpdates,

    /// Occurs when bot tries to get file by invalid file id.
    ///
    /// May happen in methods:
    /// 1. [`GetFile`]
    ///
    /// [`GetFile`]: crate::payloads::GetFile
    #[serde(rename = "Bad Request: invalid file id")]
    #[error("Bad Request: invalid file id")]
    FileIdInvalid,

    /// Error which is not known to `teloxide`.
    ///
    /// If you've received this error, please [open an issue] with the
    /// description of the error.
    ///
    /// [open an issue]: https://github.com/teloxide/teloxide/issues/new
    #[error("Unknown error: {0:?}")]
    Unknown(String),
}

impl From<reqwest::Error> for DownloadError {
    fn from(error: reqwest::Error) -> Self {
        DownloadError::Network(hide_token(error))
    }
}

impl From<reqwest::Error> for RequestError {
    fn from(error: reqwest::Error) -> Self {
        RequestError::Network(hide_token(error))
    }
}

/// Replaces token in the url in the error with `token:redacted` string.
pub(crate) fn hide_token(mut error: reqwest::Error) -> reqwest::Error {
    let url = match error.url_mut() {
        Some(url) => url,
        None => return error,
    };

    if let Some(mut segments) = url.path_segments() {
        // Usually the url looks like "bot<token>/..." or "file/bot<token>/...".
        let (beginning, segment) = match segments.next() {
            Some("file") => ("file/", segments.next()),
            segment => ("", segment),
        };

        if let Some(token) = segment.and_then(|s| s.strip_prefix("bot")) {
            // make sure that what we are about to delete looks like a bot token
            if let Some((id, secret)) = token.split_once(':') {
                // The part before the : in the token is the id of the bot.
                let id_character = |c: char| c.is_ascii_digit();

                // The part after the : in the token is the secret.
                //
                // In all bot tokens we could find the secret is 35 characters long and is
                // 0-9a-zA-Z_- only.
                //
                // It would be nice to research if TBA always has 35 character secrets or if it
                // is just a coincidence.
                const SECRET_LENGTH: usize = 35;
                let secret_character = |c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_';

                if secret.len() >= SECRET_LENGTH
                    && id.chars().all(id_character)
                    && secret.chars().all(secret_character)
                {
                    // found token, hide only the token
                    let without_token =
                        &url.path()[(beginning.len() + "/bot".len() + token.len())..];
                    let redacted = format!("{beginning}token:redacted{without_token}");

                    url.set_path(&redacted);
                    return error;
                }
            }
        }
    }

    // couldn't find token in the url, hide the whole url
    error.without_url()
}
