//! Высокоуровневый диспетчер событий с регистрацией обработчиков и длительным опросом.
//!
//! Вдохновлён проектами `maxoxide` и `teloxide`.

use std::future::Future;
use std::sync::Arc;
use std::time::Duration;

use log::{error, info, warn};

use crate::client::MaxClient;
use crate::error::Result;
use crate::types::Update;
use crate::Message;
use crate::SendMessageParamsBuilder;

// -----------------------------------------------------------------------------
// Контекст
// -----------------------------------------------------------------------------

/// Контекст, передаваемый каждому обработчику.
///
/// Содержит ссылку на клиент бота и обновление, которое вызвало этот обработчик.
#[derive(Clone)]
pub struct Context {
    pub(crate) bot: Arc<MaxClient>,
    pub update: Update,
}

impl Context {
    pub fn new(bot: Arc<MaxClient>, update: Update) -> Self {
        Self { bot, update }
    }

    /// Возвращает ссылку на клиент бота.
    pub fn bot(&self) -> &MaxClient {
        &self.bot
    }

    /// Возвращает сообщение, если обновление является созданным или отредактированным сообщением.
    pub fn message(&self) -> Option<&Message> {
        match &self.update {
            Update::MessageCreated { message, .. } => Some(message),
            Update::MessageEdited { message, .. } => Some(message),
            _ => None,
        }
    }

    /// Возвращает идентификатор чата, если обновление связано с чатом.
    pub fn chat_id(&self) -> Option<i64> {
        match &self.update {
            Update::MessageCreated { message, .. } => message.recipient.chat_id,
            Update::MessageEdited { message, .. } => message.recipient.chat_id,
            Update::MessageCallback { message, .. } => message.as_ref().and_then(|m| m.recipient.chat_id),
            Update::BotStarted { chat_id, .. } => Some(*chat_id),
            Update::BotAdded { chat_id, .. } => Some(*chat_id),
            Update::BotRemoved { chat_id, .. } => Some(*chat_id),
            Update::UserAdded { chat_id, .. } => Some(*chat_id),
            Update::UserRemoved { chat_id, .. } => Some(*chat_id),
            Update::ChatTitleChanged { chat_id, .. } => Some(*chat_id),
            _ => None,
        }
    }

    /// Возвращает идентификатор пользователя — инициатора обновления.
    pub fn user_id(&self) -> Option<i64> {
        match &self.update {
            Update::MessageCreated { message, .. } => message.sender.as_ref().map(|u| u.user_id),
            Update::MessageEdited { message, .. } => message.sender.as_ref().map(|u| u.user_id),
            Update::MessageCallback { callback, .. } => Some(callback.user.user_id),
            Update::BotStarted { user, .. } => Some(user.user_id),
            Update::BotAdded { user, .. } => Some(user.user_id),
            Update::BotRemoved { user, .. } => Some(user.user_id),
            Update::UserAdded { user, .. } => Some(user.user_id),
            Update::UserRemoved { user, .. } => Some(user.user_id),
            Update::ChatTitleChanged { user, .. } => Some(user.user_id),
            _ => None,
        }
    }

    /// Возвращает текст сообщения, если это обновление является сообщением.
    pub fn text(&self) -> Option<&str> {
        match &self.update {
            Update::MessageCreated { message, .. } => message.body.as_ref().and_then(|b| b.text.as_deref()),
            Update::MessageEdited { message, .. } => message.body.as_ref().and_then(|b| b.text.as_deref()),
            _ => None,
        }
    }

    /// Отправляет простой текстовый ответ в тот же чат.
    pub async fn reply_text(&self, text: &str) -> Result<Vec<String>> {
        let chat_id = self.chat_id().ok_or_else(|| {
            crate::error::Error::InvalidInput("Cannot reply: update has no chat_id".into())
        })?;
        let params = SendMessageParamsBuilder::new()
            .text(text)
            .chat_id(chat_id)
            .build();
        self.bot.send_message(params).await
    }

    /// Отправляет ответ в формате Markdown.
    pub async fn reply_markdown(&self, text: &str) -> Result<Vec<String>> {
        let chat_id = self.chat_id().ok_or_else(|| {
            crate::error::Error::InvalidInput("Cannot reply: update has no chat_id".into())
        })?;
        let params = SendMessageParamsBuilder::new()
            .text(text)
            .chat_id(chat_id)
            .format_markdown()
            .build();
        self.bot.send_message(params).await
    }

    /// Отвечает на запрос обратного вызова необязательным уведомлением и/или заменяющим сообщением.
    ///
    /// Работает только если обновление имеет тип `MessageCallback`.
    pub async fn answer_callback(
        &self,
        notification: Option<&str>,
        new_message: Option<crate::api::messages::SendMessageParams>,
    ) -> Result<bool> {
        match &self.update {
            Update::MessageCallback { callback, .. } => {
                let message_json = if let Some(params) = new_message {
                    let mut body = serde_json::Map::new();
                    if !params.text.is_empty() {
                        body.insert("text".into(), params.text.into());
                    }
                    if let Some(fmt) = params.format {
                        body.insert("format".into(), fmt.into());
                    }
                    if let Some(disable) = params.disable_link_preview {
                        body.insert("disable_link_preview".into(), disable.into());
                    }
                    if !params.attachments.is_empty() {
                        // Подготовка вложений
                        let attachments_json = crate::api::messages::prepare_attachments_json(&self.bot, &params.attachments).await?;
                        body.insert("attachments".into(), attachments_json);
                    }
                    Some(serde_json::Value::Object(body))
                } else {
                    None
                };
                self.bot
                    .answer_callback_query(&callback.callback_id, message_json, notification)
                    .await
            }
            _ => Err(crate::error::Error::InvalidInput(
                "answer_callback only works on MessageCallback updates".into(),
            )),
        }
    }

    /// Отвечает на запрос обратного вызова, передавая новое сообщение в виде сырого JSON.
    pub async fn answer_callback_raw(
        &self,
        notification: Option<&str>,
        message_json: Option<serde_json::Value>,
    ) -> Result<bool> {
        match &self.update {
            Update::MessageCallback { callback, .. } => {
                self.bot
                    .answer_callback_query(&callback.callback_id, message_json, notification)
                    .await
            }
            _ => Err(crate::error::Error::InvalidInput(
                "answer_callback_raw only works on MessageCallback updates".into(),
            )),
        }
    }
}

// -----------------------------------------------------------------------------
// Типаж обработчика.
// -----------------------------------------------------------------------------

/// Тип для упакованной асинхронной функции-обработчика.
pub type HandlerFn = Arc<
    dyn Fn(Context) -> std::pin::Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync,
>;

/// Преобразует переданную функцию-обработчик в `HandlerFn`.
fn make_handler<H, F>(handler: H) -> HandlerFn
where
    H: Fn(Context) -> F + Send + Sync + 'static,
    F: Future<Output = Result<()>> + Send + 'static,
{
    Arc::new(move |ctx| Box::pin(handler(ctx)))
}

// -----------------------------------------------------------------------------
// Фильтр.
// -----------------------------------------------------------------------------

/// Определяет, какие обновления интересуют обработчик.
#[derive(Clone)]
pub enum Filter {
    /// Любые обновления.
    Any,
    /// Новые сообщения (`MessageCreated`).
    Message,
    /// Отредактированные сообщения (`MessageEdited`).
    EditedMessage,
    /// Нажатия на inline-кнопки (`MessageCallback`).
    Callback,
    /// Событие запуска бота (`BotStarted`).
    BotStarted,
    /// Бот добавлен в чат (`BotAdded`).
    BotAdded,
    /// Конкретная команда (например, `"/start"`).
    Command(String),
    /// Конкретная полезная нагрузка обратного вызова.
    CallbackPayload(String),
    Custom(Arc<dyn Fn(&Update) -> bool + Send + Sync>),
}

impl Filter {
    /// Проверяет, соответствует ли обновление данному фильтру.
    fn matches(&self, update: &Update) -> bool {
        match self {
            Filter::Any => true,
            Filter::Message => matches!(update, Update::MessageCreated { .. }),
            Filter::EditedMessage => matches!(update, Update::MessageEdited { .. }),
            Filter::Callback => matches!(update, Update::MessageCallback { .. }),
            Filter::BotStarted => matches!(update, Update::BotStarted { .. }),
            Filter::BotAdded => matches!(update, Update::BotAdded { .. }),
            Filter::Command(cmd) => {
                if let Update::MessageCreated { message, .. } = update {
                    message
                        .body
                        .as_ref()
                        .and_then(|b| b.text.as_deref())
                        .map(|t| t.starts_with(cmd))
                        .unwrap_or(false)
                } else {
                    false
                }
            }
            Filter::CallbackPayload(payload) => {
                if let Update::MessageCallback { callback, .. } = update {
                    &callback.payload == payload.as_str()
                } else {
                    false
                }
            }
            Filter::Custom(pred) => pred(update),
        }
    }
}

// -----------------------------------------------------------------------------
// Диспетчер.
// -----------------------------------------------------------------------------

/// Диспетчер направляет входящие обновления зарегистрированным обработчикам.
///
/// Обработчики перебираются в порядке регистрации; выполняется первый подходящий.
pub struct Dispatcher {
    bot: Arc<MaxClient>,
    handlers: Vec<(Filter, HandlerFn)>,
    error_handler: Option<Arc<dyn Fn(crate::error::Error) + Send + Sync>>,
    poll_timeout: u32,
    poll_limit: u32,
}

impl Dispatcher {
    /// Создаёт новый диспетчер для переданного клиента бота.
    pub fn new(bot: MaxClient) -> Self {
        Self {
            bot: Arc::new(bot),
            handlers: Vec::new(),
            error_handler: None,
            poll_timeout: 30,
            poll_limit: 100,
        }
    }

    /// Устанавливает глобальный обработчик ошибок, вызываемый при ошибках в обработчиках.
    pub fn on_error<F>(mut self, f: F) -> Self
    where
        F: Fn(crate::error::Error) + Send + Sync + 'static,
    {
        self.error_handler = Some(Arc::new(f));
        self
    }

    /// Устанавливает задержку длительного опроса в секундах (по умолчанию 30, максимум 90).
    pub fn poll_timeout(mut self, secs: u32) -> Self {
        self.poll_timeout = secs;
        self
    }

    /// Устанавливает максимальное количество обновлений за один запрос (по умолчанию 100, максимум 100).
    pub fn poll_limit(mut self, limit: u32) -> Self {
        self.poll_limit = limit;
        self
    }

    // -------------------------------------------------------------------------
    // Регистрация обработчиков.
    // -------------------------------------------------------------------------

    /// Регистрирует обработчик для любых обновлений.
    pub fn on<H, F>(&mut self, handler: H) -> &mut Self
    where
        H: Fn(Context) -> F + Send + Sync + 'static,
        F: Future<Output = Result<()>> + Send + 'static,
    {
        self.handlers.push((Filter::Any, make_handler(handler)));
        self
    }

    /// Регистрирует обработчик для новых сообщений.
    pub fn on_message<H, F>(&mut self, handler: H) -> &mut Self
    where
        H: Fn(Context) -> F + Send + Sync + 'static,
        F: Future<Output = Result<()>> + Send + 'static,
    {
        self.handlers.push((Filter::Message, make_handler(handler)));
        self
    }

    /// Регистрирует обработчик для отредактированных сообщений.
    pub fn on_edited_message<H, F>(&mut self, handler: H) -> &mut Self
    where
        H: Fn(Context) -> F + Send + Sync + 'static,
        F: Future<Output = Result<()>> + Send + 'static,
    {
        self.handlers.push((Filter::EditedMessage, make_handler(handler)));
        self
    }

    /// Регистрирует обработчик для нажатий на inline-кнопки.
    pub fn on_callback<H, F>(&mut self, handler: H) -> &mut Self
    where
        H: Fn(Context) -> F + Send + Sync + 'static,
        F: Future<Output = Result<()>> + Send + 'static,
    {
        self.handlers.push((Filter::Callback, make_handler(handler)));
        self
    }

    /// Регистрирует обработчик для события запуска бота.
    pub fn on_bot_started<H, F>(&mut self, handler: H) -> &mut Self
    where
        H: Fn(Context) -> F + Send + Sync + 'static,
        F: Future<Output = Result<()>> + Send + 'static,
    {
        self.handlers.push((Filter::BotStarted, make_handler(handler)));
        self
    }

    /// Регистрирует обработчик для конкретной команды (например, `"/start"`).
    pub fn on_command<H, F>(&mut self, command: impl Into<String>, handler: H) -> &mut Self
    where
        H: Fn(Context) -> F + Send + Sync + 'static,
        F: Future<Output = Result<()>> + Send + 'static,
    {
        self.handlers
            .push((Filter::Command(command.into()), make_handler(handler)));
        self
    }

    /// Регистрирует обработчик для конкретного payload'а callback'а.
    pub fn on_callback_payload<H, F>(&mut self, payload: impl Into<String>, handler: H) -> &mut Self
    where
        H: Fn(Context) -> F + Send + Sync + 'static,
        F: Future<Output = Result<()>> + Send + 'static,
    {
        self.handlers.push((
            Filter::CallbackPayload(payload.into()),
            make_handler(handler),
        ));
        self
    }

    /// Регистрирует обработчик с пользовательским фильтром.
    pub fn on_filter<P, H, F>(&mut self, predicate: P, handler: H) -> &mut Self
    where
        P: Fn(&Update) -> bool + Send + Sync + 'static,
        H: Fn(Context) -> F + Send + Sync + 'static,
        F: Future<Output = Result<()>> + Send + 'static,
    {
        self.handlers
            .push((Filter::Custom(Arc::new(predicate)), make_handler(handler)));
        self
    }

    // -------------------------------------------------------------------------
    // Внутренние методы диспетчеризации.
    // -------------------------------------------------------------------------

    /// Отправляет одно обновление первому подходящему обработчику.
    pub async fn dispatch(&self, update: Update) {
        for (filter, handler) in &self.handlers {
            if filter.matches(&update) {
                let ctx = Context::new(self.bot.clone(), update);
                if let Err(e) = handler(ctx).await {
                    if let Some(eh) = &self.error_handler {
                        eh(e);
                    } else {
                        error!("Handler error: {}", e);
                    }
                }
                break;
            }
        }
    }

    // -------------------------------------------------------------------------
    // Цикл длительного опроса.
    // -------------------------------------------------------------------------

    /// Запускает цикл длительного опроса. Работает бесконечно, пока процесс не будет завершён.
    pub async fn start_polling(self) {
        let me = match self.bot.get_me().await {
            Ok(me) => me,
            Err(e) => {
                error!("Failed to fetch bot info: {}", e);
                return;
            }
        };
        info!(
            "Bot @{} started (long polling)",
            me.user.username.as_deref().unwrap_or("unknown")
        );

        let mut marker = None;
        loop {
            let params = crate::api::updates::GetUpdatesParams {
                marker,
                limit: Some(self.poll_limit),
                timeout: Some(self.poll_timeout),
                types: vec![], // Пустой список означает все типы
            };
            match self.bot.get_updates(params).await {
                Ok((updates, new_marker)) => {
                    marker = new_marker;
                    for update in updates {
                        self.dispatch(update).await;
                    }
                }
                Err(e) => {
                    warn!("Polling error: {} — retrying in 5s", e);
                    tokio::time::sleep(Duration::from_secs(5)).await;
                }
            }
        }
    }
}
