use std::collections::HashMap;

use crate::callback_commands::CallbackCommands;
use crate::db_controller::Controller;
use crate::messages::*;
use crate::{commands::CommandHandler, commands::Commands, config::Args};
use migration::DbErr;
use strfmt::Format;

use teloxide::utils::{command::BotCommands, markdown::escape};
use teloxide::{
    prelude::*, types::ForwardedFrom, types::InlineQueryResult, types::InlineQueryResultArticle,
    types::InputMessageContent, types::InputMessageContentText, types::ParseMode,
    types::ReplyMarkup, types::UpdateKind, RequestError,
};
use wd_log::{log_debug_ln, log_error_ln, log_info_ln, log_panic, log_warn_ln};

pub struct BotServer {
    pub controller: Controller,
    bot: Bot,
}

impl BotServer {
    /// Create new bot
    pub async fn new(config: Args) -> Result<Self, DbErr> {
        Ok(Self {
            bot: Bot::new(config.tgbot_token)
                .set_api_url(reqwest::Url::parse(config.api_url.as_str()).unwrap()),
            controller: Controller::new(config.database_uri).await?,
        })
    }

    pub async fn init(&self) -> Result<(), DbErr> {
        self.controller.migrate().await
    }

    /// Run the bot
    pub async fn run(&self) {
        match self.bot.get_me().send().await {
            Ok(result) => log_info_ln!(
                "connect succeed: id={}, botname=\"{}\"",
                result.id,
                result.username()
            ),
            Err(error) => log_panic!("{}", error),
        }

        self.register_commands().await;

        let mut offset_id = 0;

        loop {
            let updates = match self.bot.get_updates().offset(offset_id).send().await {
                Ok(it) => it,
                _ => continue,
            };
            for update in updates {
                self.update_handler(&update).await;
                offset_id = update.id + 1;
            }
        }
    }

    async fn register_commands(&self) {
        if let Err(error) = self
            .bot
            .set_my_commands(Commands::bot_commands())
            .send()
            .await
        {
            self.default_error_handler(&error);
        } else {
            log_info_ln!("commands registered")
        }
    }

    async fn update_handler(&self, update: &Update) {
        match &update.kind {
            UpdateKind::Message(ref message) => self.message_handler(message).await,
            UpdateKind::InlineQuery(inline_query) => self.inline_query_hander(inline_query).await,
            UpdateKind::CallbackQuery(callback) => self.callback_handler(callback).await,
            UpdateKind::ChosenInlineResult(result) => {
                self.chosen_inline_result_handler(result).await;
            }
            kind => self.default_update_hander(&kind).await,
        }
    }

    async fn default_update_hander(&self, update_kind: &UpdateKind) {
        log_debug_ln!("non-supported kind {:?}", update_kind);
    }

    async fn chosen_inline_result_handler(&self, result: &ChosenInlineResult) {
        log_debug_ln!("chosen_result={:?}", result);

        if let Err(error) = self
            .controller
            .update_record_hot(result.result_id.parse::<i64>().unwrap())
            .await
        {
            self.controller.err_handler(error);
        }
    }

    async fn callback_handler(&self, callback: &CallbackQuery) {
        log_debug_ln!("callback={:#?}", callback);

        let message = match &callback.message {
            Some(msg) => msg,
            None => return,
        };

        let text = match &callback.data {
            Some(text) => text,
            None => return,
        };

        let bot_username = match self.bot.get_me().send().await {
            Ok(result) => result.username.to_owned(),
            Err(error) => {
                self.default_error_handler(&error);
                return;
            }
        };

        let bot_username = match bot_username {
            Some(b) => b,
            None => return,
        };

        let commands = match CallbackCommands::parse(text, bot_username) {
            Ok(c) => c,
            Err(error) => {
                log_warn_ln!("{}", error);
                return;
            }
        };

        match commands {
            CallbackCommands::Page {
                msg_id: _,
                username,
                page,
            } => {
                let (msg, keyboard) = match CommandHandler::record_msg_genrator(
                    self,
                    message,
                    username.as_str(),
                    page,
                )
                .await
                {
                    Some(d) => d,
                    None => return,
                };

                self.edit_text_reply_with_inline_key(message, message.id, msg.as_str(), keyboard)
                    .await;

                match self.bot.answer_callback_query(&callback.id).send().await {
                    Ok(_) => (),
                    Err(error) => self.default_error_handler(&error),
                }
            }
            CallbackCommands::Default => return,
        }
    }

    async fn inline_query_hander(&self, inline_query: &InlineQuery) {
        let results = match self
            .controller
            .get_records_by_keywords(&inline_query.query)
            .await
        {
            Ok(results) => results,
            Err(error) => {
                self.controller.err_handler(error);
                return;
            }
        };

        let mut r: Vec<InlineQueryResult> = vec![];
        for (record, o_user) in results.current_data.iter() {
            let user = match o_user {
                Some(user) => user,
                None => continue,
            };

            let username = match &user.username {
                Some(username) => username,
                None => continue,
            };

            r.push(InlineQueryResult::Article(InlineQueryResultArticle {
                id: record.id.to_string(),
                title: record.message.to_owned(),
                input_message_content: InputMessageContent::Text(InputMessageContentText {
                    message_text: format!(
                        "*{}*: {}",
                        username.trim_start_matches("@"),
                        escape(&record.message)
                    ),
                    parse_mode: Some(ParseMode::MarkdownV2),
                    entities: None,
                    disable_web_page_preview: Some(true),
                }),
                reply_markup: None,
                url: None,
                hide_url: None,
                description: Some(format!("By: {}", username)),
                thumb_url: None,
                thumb_width: None,
                thumb_height: None,
            }));
        }

        if let Err(error) = self
            .bot
            .answer_inline_query(&inline_query.id, r.into_iter())
            .send()
            .await
        {
            self.default_error_handler(&error);
        }
    }

    async fn message_handler(&self, message: &Message) {
        if let Some(data) = &message.text() {
            self.text_message_heandler(message, data).await
        } else {
            self.default_message_handler(message).await
        }
    }

    async fn text_message_heandler(&self, message: &Message, data: &str) {
        let forward = match message.forward() {
            Some(forward) => forward,
            None => {
                if data.starts_with("/") {
                    self.command_hanler(message).await;
                } else {
                    self.send_text_reply(message, BOT_TEXT_FORWARDED_ONLY).await;
                }
                return;
            }
        };

        match &forward.from {
            ForwardedFrom::User(user) if !user.is_bot => {
                let username = match &user.username {
                    Some(username) => format!("@{}", username),
                    None => user.first_name.to_owned(),
                };

                if let Err(err) = self
                    .controller
                    .add_record(user.id.0.try_into().unwrap(), &username, data.to_string())
                    .await
                {
                    self.controller.err_handler(err);
                    return;
                }
                let mut vars = HashMap::new();
                vars.insert("data".to_string(), data);

                self.send_text_reply(message, &BOT_TEXT_NOTED.format(&vars).unwrap())
                    .await;

                let from = match message.from() {
                    Some(from) => from,
                    None => return,
                };

                if from.id == user.id {
                    return;
                }

                if match self
                    .controller
                    .get_user_notify(&user.id.0.try_into().unwrap())
                    .await
                {
                    Ok(notify) => notify,
                    Err(error) => {
                        log_error_ln!("{}", error);
                        return;
                    }
                } {
                    let mut vars = HashMap::new();
                    let user_id = from.id.to_string();
                    let data = data.to_string();

                    vars.insert("username".to_string(), &from.first_name);
                    vars.insert("user_id".to_string(), &user_id);
                    vars.insert("data".to_string(), &data);

                    match self
                        .bot
                        .send_message(user.id, &BOT_TEXT_NOTICE.format(&vars).unwrap())
                        .send()
                        .await
                    {
                        Ok(result) => {
                            log_debug_ln!("message sent {:?}", result)
                        }
                        Err(err) => self.default_error_handler(&err),
                    }
                }
            }
            ForwardedFrom::User(_) => {
                self.send_text_reply(message, BOT_TEXT_NO_BOT).await;
            }
            ForwardedFrom::SenderName(_) => {
                self.send_text_reply(message, BOT_TEXT_USER_PRIVATE).await;
            }
            _ => {
                self.send_text_message(message, BOT_TEXT_USER_ONLY).await;
            }
        }
    }

    async fn command_hanler(&self, message: &Message) {
        let msg = match message.text() {
            Some(msg) => msg,
            None => return,
        };

        let bot_username = match self.bot.get_me().send().await {
            Ok(result) => result.username.to_owned(),
            Err(error) => {
                self.default_error_handler(&error);
                return;
            }
        };

        let bot_username = match bot_username {
            Some(b) => b,
            None => return,
        };

        let commands = match Commands::parse(msg, bot_username) {
            Ok(c) => c,
            Err(error) => {
                log_warn_ln!("{}", error);
                return;
            }
        };

        match commands {
            Commands::Help => CommandHandler::help_handler(&self, message).await,
            Commands::About => CommandHandler::about_handler(&self, message).await,
            Commands::Mute => CommandHandler::notify_handler(&self, message, true).await,
            Commands::Unmute => CommandHandler::notify_handler(&self, message, false).await,
            Commands::List { mut username } => {
                if username == "me" {
                    if let Some(from) = message.from() {
                        if let Some(_username) = &from.username {
                            username = format!("@{}", _username);
                        }
                    }
                }

                if username.starts_with("@") {
                    // always start from page=0
                    CommandHandler::list_handler(&self, message, &username, 0).await;
                } else {
                    self.send_text_reply(message, BOT_TEXT_SHOULD_START_WITH_AT)
                        .await;
                }
            }
            Commands::Del { id } => CommandHandler::del_handler(&self, message, id).await,
            Commands::Start => CommandHandler::setup_handler(&self, message).await,
        }
    }

    fn default_error_handler(&self, error: &RequestError) {
        log_error_ln!("{:?}", error);
    }

    async fn default_message_handler(&self, message: &Message) {
        log_debug_ln!(
            "non-spported message {:?} from `{:?}`",
            message.kind,
            message.from()
        );
        self.send_text_reply(message, BOT_TEXT_MESSAGE_ONLY).await;
    }

    pub async fn send_text_message(&self, message: &Message, text: &str) -> Option<i32> {
        match &self
            .bot
            .send_message(message.chat.id, text)
            .parse_mode(ParseMode::MarkdownV2)
            .send()
            .await
        {
            Ok(result) => {
                log_debug_ln!("message sent {:?}", result);
                Some(result.id)
            }
            Err(error) => {
                self.default_error_handler(error);
                return None;
            }
        }
    }

    pub async fn send_text_reply(&self, message: &Message, text: &str) -> Option<i32> {
        match &self
            .bot
            .send_message(message.chat.id, text)
            .reply_to_message_id(message.id)
            .parse_mode(ParseMode::MarkdownV2)
            .send()
            .await
        {
            Ok(result) => {
                log_debug_ln!("reply sent {:?}", result);
                Some(result.id)
            }
            Err(error) => {
                self.default_error_handler(error);
                None
            }
        }
    }

    pub async fn edit_text_reply_with_inline_key(
        &self,
        message: &Message,
        msg_id: i32,
        text: &str,
        keyboard: ReplyMarkup,
    ) {
        let keyboard = match keyboard {
            ReplyMarkup::InlineKeyboard(keyboard) => keyboard,
            _ => return,
        };

        match &self
            .bot
            .edit_message_text(message.chat.id, msg_id, text)
            .reply_markup(keyboard)
            .parse_mode(ParseMode::MarkdownV2)
            .send()
            .await
        {
            Ok(result) => log_debug_ln!("reply sent {:?}", result),
            Err(error) => self.default_error_handler(error),
        }
    }
}