#![allow(async_fn_in_trait, reason = "I don't care about Send")]

use std::{fmt::Debug, path::PathBuf};

use serde::{de::DeserializeOwned, ser::Serialize};

use super::{
    games::GameHighScore,
    gifts::{Gifts, OwnedGifts},
    inline_mode::{
        PreparedInlineMessage, PreparedKeyboardButton, SentGuestMessage, SentWebAppMessage,
    },
    input_file::HasInputFile,
    input_media::{
        InputMedia, InputPollMedia, InputPollOptionMedia, InputProfilePhoto, InputStoryContent,
        MediaGroupInputMedia,
    },
    methods::{
        AddStickerToSetParams, CreateNewStickerSetParams, EditMessageMediaParams, EditStoryParams,
        PostStoryParams, SetBusinessAccountProfilePhotoParams, SetChatPhotoParams,
        SetMyProfilePhotoParams, UploadStickerFileParams,
    },
    payments::{StarAmount, StarTransactions},
    response::{MessageOrBool, MethodResponse},
    stickers::{Sticker, StickerSet},
    types::{
        BotAccessSettings, BotCommand, BotDescription, BotName, BotShortDescription,
        BusinessConnection, ChatAdministratorRights, ChatFullInfo, ChatInviteLink, ChatMember,
        File, ForumTopic, MenuButton, Message, MessageId, Poll, Story, User, UserChatBoosts,
        UserProfileAudios, UserProfilePhotos,
    },
    updates::{Update, WebhookInfo},
};

macro_rules! request {
    ($name:ident, $return:ty) => {
        paste::paste! {
            #[allow(async_fn_in_trait)]
            #[doc = "Call the `" $name "` method.\n\nSee <https://core.telegram.org/bots/api#" $name:lower ">."]
            #[inline(always)]
            async fn [<$name:snake>] (
                &self,
                params: &crate::methods::[<$name:camel Params>],
            ) -> Result<MethodResponse<$return>, Self::Error> {
                self.request(stringify!($name), Some(params)).await
            }
        }
    }
}

/// request no body
macro_rules! request_nb {
    ($name:ident, $return:ty) => {
        paste::paste! {
            #[allow(async_fn_in_trait)]
            #[doc = "Call the `" $name "` method.\n\nSee <https://core.telegram.org/bots/api#" $name:lower ">."]
            #[inline(always)]
            async fn [<$name:snake>] (
                &self,
            ) -> Result<MethodResponse<$return>, Self::Error> {
                let params: Option<()> = None;
                self.request(stringify!($name), params).await
            }
        }
    }
}

/// request with some properties utilizing [`HasInputFile`]
macro_rules! request_f {
    ($name:ident, $return:ty, $($fileproperty:ident),+) => {
        paste::paste! {
            #[allow(async_fn_in_trait)]
            #[doc = "Call the `" $name "` method.\n\nSee <https://core.telegram.org/bots/api#" $name:lower ">."]
            async fn [<$name:snake>] (
                &self,
                params: &crate::methods::[<$name:camel Params>],
            ) -> Result<MethodResponse<$return>, Self::Error> {
                let mut files = Vec::new();
                $(
                    if let Some(path) = params.$fileproperty.clone_path() {
                        files.push((stringify!($fileproperty), path));
                    }
                )+
                self.request_with_possible_form_data(stringify!($name), params, files).await
            }
        }
    }
}

pub trait AsyncTelegramApi {
    type Error;

    request!(getUpdates, Vec<Update>);
    request!(sendMessage, Message);
    request!(setWebhook, bool);
    request!(deleteWebhook, bool);
    request_nb!(getWebhookInfo, WebhookInfo);
    request_nb!(getMe, User);
    request_nb!(logOut, bool);
    request_nb!(close, bool);
    request!(forwardMessage, Message);
    request!(forwardMessages, Vec<MessageId>);
    request!(copyMessage, MessageId);
    request!(copyMessages, Vec<MessageId>);
    request_f!(sendPhoto, Message, photo);
    request_f!(sendAudio, Message, audio, thumbnail);

    async fn send_media_group(
        &self,
        params: &crate::methods::SendMediaGroupParams,
    ) -> Result<MethodResponse<Vec<Message>>, Self::Error> {
        let mut files = Vec::new();

        macro_rules! replace_attach {
            ($base:ident. $property:ident) => {
                if let Some(file) = $base.$property.replace_attach_dyn(|| files.len()) {
                    files.push(file);
                }
            };
        }

        let mut params = params.clone();
        for media in &mut params.media {
            match media {
                MediaGroupInputMedia::Audio(audio) => {
                    replace_attach!(audio.media);
                    replace_attach!(audio.thumbnail);
                }
                MediaGroupInputMedia::Document(document) => {
                    replace_attach!(document.media);
                }
                MediaGroupInputMedia::LivePhoto(live_photo) => {
                    replace_attach!(live_photo.media);
                    replace_attach!(live_photo.photo);
                }
                MediaGroupInputMedia::Photo(photo) => {
                    replace_attach!(photo.media);
                }
                MediaGroupInputMedia::Video(video) => {
                    replace_attach!(video.media);
                    replace_attach!(video.cover);
                    replace_attach!(video.thumbnail);
                }
            }
        }

        let files_with_str_names = files
            .iter()
            .map(|(key, path)| (key.as_str(), path.clone()))
            .collect();

        self.request_with_possible_form_data("sendMediaGroup", &params, files_with_str_names)
            .await
    }

    request_f!(sendDocument, Message, document, thumbnail);
    request_f!(sendVideo, Message, video, cover, thumbnail);
    request_f!(sendAnimation, Message, animation, thumbnail);
    request_f!(sendVoice, Message, voice);
    request_f!(sendVideoNote, Message, video_note, thumbnail);
    request_f!(sendLivePhoto, Message, media, photo);
    request!(sendPaidMedia, Message);
    request!(sendLocation, Message);
    request!(editMessageLiveLocation, MessageOrBool);
    request!(stopMessageLiveLocation, MessageOrBool);
    request!(sendChecklist, MessageOrBool);
    request!(editMessageChecklist, MessageOrBool);
    request!(sendVenue, Message);
    request!(sendContact, Message);
    async fn send_poll(
        &self,
        params: &crate::methods::SendPollParams,
    ) -> Result<MethodResponse<Message>, Self::Error> {
        fn replace_poll_media_files(
            media: &mut InputPollMedia,
            files: &mut Vec<(String, PathBuf)>,
        ) {
            macro_rules! replace_attach {
                ($base:ident. $property:ident) => {
                    if let Some(file) = $base.$property.replace_attach_dyn(|| files.len()) {
                        files.push(file);
                    }
                };
            }

            match media {
                InputPollMedia::Animation(animation) => {
                    replace_attach!(animation.media);
                    replace_attach!(animation.thumbnail);
                }
                InputPollMedia::Audio(audio) => {
                    replace_attach!(audio.media);
                    replace_attach!(audio.thumbnail);
                }
                InputPollMedia::Document(document) => {
                    replace_attach!(document.media);
                    replace_attach!(document.thumbnail);
                }
                InputPollMedia::LivePhoto(live_photo) => {
                    replace_attach!(live_photo.media);
                    replace_attach!(live_photo.photo);
                }

                InputPollMedia::Photo(photo) => {
                    replace_attach!(photo.media);
                }

                InputPollMedia::Video(video) => {
                    replace_attach!(video.media);
                    replace_attach!(video.cover);
                    replace_attach!(video.thumbnail);
                }
                _ => {}
            }
        }

        fn replace_poll_option_media_files(
            media: &mut InputPollOptionMedia,
            files: &mut Vec<(String, PathBuf)>,
        ) {
            macro_rules! replace_attach {
                ($base:ident. $property:ident) => {
                    if let Some(file) = $base.$property.replace_attach_dyn(|| files.len()) {
                        files.push(file);
                    }
                };
            }

            match media {
                InputPollOptionMedia::Animation(animation) => {
                    replace_attach!(animation.media);
                    replace_attach!(animation.thumbnail);
                }
                InputPollOptionMedia::LivePhoto(live_photo) => {
                    replace_attach!(live_photo.media);
                    replace_attach!(live_photo.photo);
                }
                InputPollOptionMedia::Photo(photo) => {
                    replace_attach!(photo.media);
                }
                InputPollOptionMedia::Sticker(sticker) => {
                    replace_attach!(sticker.media);
                }
                InputPollOptionMedia::Video(video) => {
                    replace_attach!(video.media);
                    replace_attach!(video.cover);
                    replace_attach!(video.thumbnail);
                }
                _ => {}
            }
        }

        let mut files = Vec::new();
        let mut params = params.clone();

        if let Some(media) = &mut params.media {
            replace_poll_media_files(media, &mut files);
        }
        if let Some(media) = &mut params.explanation_media {
            replace_poll_media_files(media, &mut files);
        }
        for option in &mut params.options {
            if let Some(media) = &mut option.media {
                replace_poll_option_media_files(media, &mut files);
            }
        }

        let files_with_str_names = files
            .iter()
            .map(|(key, path)| (key.as_str(), path.clone()))
            .collect();

        self.request_with_possible_form_data("sendPoll", &params, files_with_str_names)
            .await
    }

    request!(sendDice, Message);
    request!(sendMessageDraft, bool);
    request!(sendChatAction, bool);
    request!(setMessageReaction, bool);
    request!(getUserProfilePhotos, UserProfilePhotos);
    request!(getUserProfileAudios, UserProfileAudios);
    request!(getManagedBotToken, String);
    request!(replaceManagedBotToken, String);
    request!(getManagedBotAccessSettings, BotAccessSettings);
    request!(setManagedBotAccessSettings, bool);
    request!(setUserEmojiStatus, bool);
    request!(getFile, File);
    request!(banChatMember, bool);
    request!(unbanChatMember, bool);
    request!(restrictChatMember, bool);
    request!(promoteChatMember, bool);
    request!(setChatAdministratorCustomTitle, bool);
    request!(setChatMemberTag, bool);
    request!(banChatSenderChat, bool);
    request!(unbanChatSenderChat, bool);
    request!(setChatPermissions, bool);
    request!(exportChatInviteLink, String);
    request!(createChatInviteLink, ChatInviteLink);
    request!(editChatInviteLink, ChatInviteLink);
    request!(createChatSubscriptionInviteLink, ChatInviteLink);
    request!(editChatSubscriptionInviteLink, ChatInviteLink);
    request!(revokeChatInviteLink, ChatInviteLink);
    request!(approveChatJoinRequest, bool);
    request!(declineChatJoinRequest, bool);

    async fn set_chat_photo(
        &self,
        params: &SetChatPhotoParams,
    ) -> Result<MethodResponse<bool>, Self::Error> {
        let photo = &params.photo;
        self.request_with_form_data("setChatPhoto", params, vec![("photo", photo.path.clone())])
            .await
    }

    request!(deleteChatPhoto, bool);
    request!(setChatTitle, bool);
    request!(setChatDescription, bool);
    request!(pinChatMessage, bool);
    request!(unpinChatMessage, bool);
    request!(unpinAllChatMessages, bool);
    request!(leaveChat, bool);
    request!(getChat, ChatFullInfo);
    request!(getChatAdministrators, Vec<ChatMember>);
    request!(getChatMemberCount, u32);
    request!(getChatMember, ChatMember);
    request!(getUserPersonalChatMessages, Vec<Message>);
    request!(setChatStickerSet, bool);
    request!(deleteChatStickerSet, bool);
    request_nb!(getForumTopicIconStickers, Vec<Sticker>);
    request!(createForumTopic, ForumTopic);
    request!(editForumTopic, bool);
    request!(closeForumTopic, bool);
    request!(reopenForumTopic, bool);
    request!(deleteForumTopic, bool);
    request!(unpinAllForumTopicMessages, bool);
    request!(editGeneralForumTopic, bool);
    request!(closeGeneralForumTopic, bool);
    request!(reopenGeneralForumTopic, bool);
    request!(hideGeneralForumTopic, bool);
    request!(unhideGeneralForumTopic, bool);
    request!(answerCallbackQuery, bool);
    request!(answerGuestQuery, SentGuestMessage);
    request!(getUserChatBoosts, UserChatBoosts);
    request!(getBusinessConnection, BusinessConnection);
    request!(getMyCommands, Vec<BotCommand>);
    request!(setMyCommands, bool);
    request!(deleteMyCommands, bool);
    request!(setMyName, bool);
    request!(getMyName, BotName);
    request!(setMyDescription, bool);
    request!(getMyDescription, BotDescription);
    request!(setMyShortDescription, bool);
    request!(getMyShortDescription, BotShortDescription);

    async fn set_my_profile_photo(
        &self,
        params: &SetMyProfilePhotoParams,
    ) -> Result<MethodResponse<bool>, Self::Error> {
        let mut files = Vec::new();

        let mut params = params.clone();
        match &mut params.photo {
            InputProfilePhoto::Static(photo_static) => {
                if let Some(file) = photo_static.photo.replace_attach("photo_static") {
                    files.push(("photo_static", file));
                }
            }
            InputProfilePhoto::Animated(photo_animated) => {
                if let Some(file) = photo_animated.animation.replace_attach("photo_animated") {
                    files.push(("photo_animated", file));
                }
            }
        }

        self.request_with_possible_form_data("setMyProfilePhoto", params, files)
            .await
    }

    request_nb!(removeMyProfilePhoto, bool);
    request!(answerInlineQuery, bool);
    request!(editMessageText, MessageOrBool);
    request!(editMessageCaption, MessageOrBool);

    async fn edit_message_media(
        &self,
        params: &EditMessageMediaParams,
    ) -> Result<MethodResponse<MessageOrBool>, Self::Error> {
        let mut files = Vec::new();

        macro_rules! replace_attach {
            ($base:ident. $property:ident) => {{
                const NAME: &str = concat!(stringify!($base), "_", stringify!($property));
                if let Some(file) = $base.$property.replace_attach(NAME) {
                    files.push((NAME, file));
                }
            }};
        }

        let mut params = params.clone();
        match &mut params.media {
            InputMedia::Animation(animation) => {
                replace_attach!(animation.media);
                replace_attach!(animation.thumbnail);
            }
            InputMedia::Document(document) => {
                replace_attach!(document.media);
                replace_attach!(document.thumbnail);
            }
            InputMedia::Audio(audio) => {
                replace_attach!(audio.media);
                replace_attach!(audio.thumbnail);
            }
            InputMedia::LivePhoto(live_photo) => {
                replace_attach!(live_photo.media);
                replace_attach!(live_photo.photo);
            }
            InputMedia::Photo(photo) => {
                replace_attach!(photo.media);
            }
            InputMedia::Video(video) => {
                replace_attach!(video.media);
                replace_attach!(video.cover);
                replace_attach!(video.thumbnail);
            }
        }

        self.request_with_possible_form_data("editMessageMedia", &params, files)
            .await
    }

    request!(editMessageReplyMarkup, MessageOrBool);
    request!(stopPoll, Poll);
    request!(approveSuggestedPost, bool);
    request!(declineSuggestedPost, bool);
    request!(deleteMessage, bool);
    request!(deleteMessages, bool);
    request!(deleteMessageReaction, bool);
    request!(deleteAllMessageReactions, bool);
    request_f!(sendSticker, Message, sticker);
    request!(getStickerSet, StickerSet);

    async fn upload_sticker_file(
        &self,
        params: &UploadStickerFileParams,
    ) -> Result<MethodResponse<File>, Self::Error> {
        let sticker = &params.sticker;
        self.request_with_form_data(
            "uploadStickerFile",
            params,
            vec![("sticker", sticker.path.clone())],
        )
        .await
    }

    async fn create_new_sticker_set(
        &self,
        params: &CreateNewStickerSetParams,
    ) -> Result<MethodResponse<bool>, Self::Error> {
        let mut files = Vec::new();

        let mut params = params.clone();
        for (index, sticker) in params.stickers.iter_mut().enumerate() {
            if let Some(file) = sticker.sticker.replace_attach_dyn(|| index) {
                files.push(file);
            }
        }

        let files_with_str_names = files
            .iter()
            .map(|(key, path)| (key.as_str(), path.clone()))
            .collect();

        self.request_with_possible_form_data("createNewStickerSet", &params, files_with_str_names)
            .await
    }

    request!(getCustomEmojiStickers, Vec<Sticker>);

    async fn add_sticker_to_set(
        &self,
        params: &AddStickerToSetParams,
    ) -> Result<MethodResponse<bool>, Self::Error> {
        let mut files = Vec::new();
        let mut params = params.clone();
        if let Some(file) = params.sticker.sticker.replace_attach("sticker_upload") {
            files.push(("sticker_upload", file));
        }
        self.request_with_possible_form_data("addStickerToSet", params, files)
            .await
    }

    request!(setStickerPositionInSet, bool);
    request!(deleteStickerFromSet, bool);
    request!(replaceStickerInSet, bool);
    request!(setStickerEmojiList, bool);
    request!(setStickerKeywords, bool);
    request!(setStickerMaskPosition, bool);
    request!(setStickerSetTitle, bool);
    request_f!(setStickerSetThumbnail, bool, thumbnail);
    request!(setCustomEmojiStickerSetThumbnail, bool);
    request!(deleteStickerSet, bool);
    request_nb!(getAvailableGifts, Gifts);
    request!(sendGift, bool);
    request!(giftPremiumSubscription, bool);
    request!(verifyUser, bool);
    request!(verifyChat, bool);
    request!(removeUserVerification, bool);
    request!(removeChatVerification, bool);
    request!(readBusinessMessage, bool);
    request!(deleteBusinessMessages, bool);
    request!(setBusinessAccountName, bool);
    request!(setBusinessAccountUsername, bool);
    request!(setBusinessAccountBio, bool);

    async fn set_business_account_profile_photo(
        &self,
        params: &SetBusinessAccountProfilePhotoParams,
    ) -> Result<MethodResponse<bool>, Self::Error> {
        let mut files = Vec::new();

        let mut params = params.clone();
        match &mut params.photo {
            InputProfilePhoto::Static(photo_static) => {
                if let Some(file) = photo_static.photo.replace_attach("photo_static") {
                    files.push(("photo_static", file));
                }
            }
            InputProfilePhoto::Animated(photo_animated) => {
                if let Some(file) = photo_animated.animation.replace_attach("photo_animated") {
                    files.push(("photo_animated", file));
                }
            }
        }

        self.request_with_possible_form_data("setBusinessAccountProfilePhoto", params, files)
            .await
    }

    request!(removeBusinessAccountProfilePhoto, bool);
    request!(setBusinessAccountGiftSettings, bool);
    request!(getBusinessAccountStarBalance, StarAmount);
    request!(transferBusinessAccountStars, bool);
    request!(getBusinessAccountGifts, OwnedGifts);
    request!(getUserGifts, OwnedGifts);
    request!(getChatGifts, OwnedGifts);
    request!(convertGiftToStars, bool);
    request!(upgradeGift, bool);
    request!(transferGift, bool);

    async fn post_story(
        &self,
        params: &PostStoryParams,
    ) -> Result<MethodResponse<Story>, Self::Error> {
        let mut files = Vec::new();

        let mut params = params.clone();

        match &mut params.content {
            InputStoryContent::Photo(photo_content) => {
                if let Some(file) = photo_content.photo.replace_attach("photo_content") {
                    files.push(("photo_content", file));
                }
            }
            InputStoryContent::Video(video_content) => {
                if let Some(file) = video_content.video.replace_attach("video_content") {
                    files.push(("video_content", file));
                }
            }
        }

        self.request_with_possible_form_data("postStory", params, files)
            .await
    }

    request!(repostStory, Story);

    async fn edit_story(
        &self,
        params: &EditStoryParams,
    ) -> Result<MethodResponse<Story>, Self::Error> {
        let mut files = Vec::new();

        let mut params = params.clone();

        match &mut params.content {
            InputStoryContent::Photo(photo_content) => {
                if let Some(file) = photo_content.photo.replace_attach("photo_content") {
                    files.push(("photo_content", file));
                }
            }
            InputStoryContent::Video(video_content) => {
                if let Some(file) = video_content.video.replace_attach("video_content") {
                    files.push(("video_content", file));
                }
            }
        }

        self.request_with_possible_form_data("editStory", params, files)
            .await
    }
    request!(deleteStory, bool);
    request!(sendInvoice, Message);
    request!(createInvoiceLink, String);
    request!(answerShippingQuery, bool);
    request!(answerPreCheckoutQuery, bool);
    request_nb!(getMyStarBalance, u32);
    request!(getStarTransactions, StarTransactions);
    request!(refundStarPayment, bool);
    request!(editUserStarSubscription, bool);
    request!(sendGame, Message);
    request!(setGameScore, MessageOrBool);
    request!(getGameHighScores, Vec<GameHighScore>);
    request!(setMyDefaultAdministratorRights, bool);
    request!(getMyDefaultAdministratorRights, ChatAdministratorRights);
    request!(answerWebAppQuery, SentWebAppMessage);
    request!(savePreparedInlineMessage, PreparedInlineMessage);
    request!(savePreparedKeyboardButton, PreparedKeyboardButton);
    request!(setChatMenuButton, bool);
    request!(getChatMenuButton, MenuButton);
    request!(unpinAllGeneralForumTopicMessages, bool);
    request!(setPassportDataErrors, bool);

    async fn request_with_possible_form_data<Params, Output>(
        &self,
        method_name: &str,
        params: Params,
        files: Vec<(&str, PathBuf)>,
    ) -> Result<Output, Self::Error>
    where
        Params: Serialize + Debug + Send,
        Output: DeserializeOwned,
    {
        if files.is_empty() {
            self.request(method_name, Some(params)).await
        } else {
            self.request_with_form_data(method_name, params, files)
                .await
        }
    }

    async fn request<Params, Output>(
        &self,
        method: &str,
        params: Option<Params>,
    ) -> Result<Output, Self::Error>
    where
        Params: Serialize + Debug + Send,
        Output: DeserializeOwned;

    async fn request_with_form_data<Params, Output>(
        &self,
        method: &str,
        params: Params,
        files: Vec<(&str, PathBuf)>,
    ) -> Result<Output, Self::Error>
    where
        Params: Serialize + Debug + Send,
        Output: DeserializeOwned;
}
