use serde::Serialize;
use std::path::PathBuf;

/// Источник файлового вложения: локальный файл или уже полученный токен.
///
/// Используется в вариантах перечисления [`Attachment`], связанных с файлами
/// (изображения, видео, аудио, документы). Позволяет библиотеке автоматически
/// загрузить локальный файл при отправке сообщения или сразу использовать
/// существующий токен.
#[derive(Debug, Clone)]
pub enum AttachmentSource {
    /// Путь к локальному файлу, который будет прочитан и загружен в MAX.
    LocalFile(PathBuf),
    /// Готовый токен вложения, полученный ранее (например, при загрузке файла).
    Token(String),
}

/// Данные контакта для вложения `contact`.
///
/// Используется при отправке контакта через [`Attachment::Contact`].
/// Все поля необязательные, поля с `None` не сериализуются в JSON.
#[derive(Debug, Clone, Serialize)]
pub struct ContactData {
    /// Имя контакта.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    /// Идентификатор контакта в MAX.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub contact_id: Option<i64>,
    /// VCF‑информация о контакте.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub vcf_info: Option<String>,
    /// Телефон контакта в формате VCF.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub vcf_phone: Option<String>,
}

/// Кнопка inline‑клавиатуры.
///
/// Может быть различных типов, определяемых полем `type`. Подробнее о типах кнопок
/// смотрите в документации API MAX.
#[derive(Debug, Clone, Serialize)]
pub struct InlineKeyboardButton {
    /// Тип кнопки: `"callback"`, `"link"`, `"request_geo_location"`, `"open_app"`, `"message"`,
    /// `"clipboard"`.
    pub r#type: String,
    /// Текст, отображаемый на кнопке.
    pub text: String,
    /// Полезная нагрузка для callback‑кнопок.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub payload: Option<String>,
    /// URL для кнопок‑ссылок.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    /// Флаг быстрой кнопки (обычно `true` для запросов контакта или геолокации).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub quick: Option<bool>,
    /// URL веб‑приложения для кнопок типа `"open_app"`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub web_app: Option<String>,
    /// Идентификатор контакта для кнопок `"open_app"`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub contact_id: Option<i64>,
    /// Полезная нагрузка для кнопок `"open_app"`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub app_payload: Option<String>,
    /// Текст сообщения, которое будет отправлено при нажатии на кнопку типа `"message"`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message_text: Option<String>,
}

/// Inline‑клавиатура (двумерный массив кнопок).
///
/// Каждый вложенный вектор представляет собой строку кнопок.
/// Используйте [`InlineKeyboardBuilder`] для удобного построения клавиатуры.
#[derive(Debug, Clone, Serialize)]
pub struct InlineKeyboard {
    /// Строки кнопок.
    pub buttons: Vec<Vec<InlineKeyboardButton>>,
}

/// Данные для вложения `share` (отсылка/предпросмотр ссылки).
///
/// Может содержать либо URL для генерации предпросмотра, либо готовый токен вложения.
#[derive(Debug, Clone, Serialize)]
pub struct ShareData {
    /// URL, на который нужно сделать отсылку.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    /// Токен уже подготовленного вложения‑ссылки.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub token: Option<String>,
}

/// Тип вложения.
///
/// Представляет все возможные вложения, которые можно отправить с сообщением
/// в MAX. Используется при формировании запроса [`SendMessageParams`][crate::SendMessageParams].
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", content = "payload")]
pub enum Attachment {
    /// Изображение: локальный файл, токен или URL.
    #[serde(rename = "image")]
    Image {
        /// Источник изображения: локальный файл или готовый токен.
        /// Не сериализуется, используется только для загрузки локальных файлов.
        #[serde(skip)]
        source: AttachmentSource,
        /// Токен ранее загруженного изображения (если есть).
        token: Option<String>,
        /// Прямой URL изображения (если есть).
        url: Option<String>,
    },
    /// Видео: локальный файл, токен или URL.
    #[serde(rename = "video")]
    Video {
        /// Источник видео.
        #[serde(skip)]
        source: AttachmentSource,
        /// Токен вложения (заполняется автоматически после загрузки).
        token: Option<String>,
        /// Адрес готового видео.
        url: Option<String>,
        /// Желаемое имя файла. Используется при скачивании по URL для правильного определения MIME-типа.
        filename: Option<String>,
    },
    /// Аудио: локальный файл, токен или URL.
    #[serde(rename = "audio")]
    Audio {
        #[serde(skip)]
        source: AttachmentSource,
        /// Токен вложения (заполняется автоматически после загрузки).
        token: Option<String>,
        /// Адрес готового аудиофайла.
        url: Option<String>,
        /// Желаемое имя файла. Используется при скачивании по URL для правильного определения MIME-типа.
        filename: Option<String>,
    },
    /// Файл: локальный файл, токен или URL.
    #[serde(rename = "file")]
    File {
        #[serde(skip)]
        source: AttachmentSource,
        /// Токен вложения (заполняется автоматически после загрузки).
        token: Option<String>,
        /// Адрес готового файла.
        url: Option<String>,
        /// Желаемое имя файла. Используется при скачивании по URL для правильного определения MIME-типа.
        filename: Option<String>,
    },
    /// Стикер.
    #[serde(rename = "sticker")]
    Sticker {
        /// Код стикера (уникальный идентификатор в системе MAX).
        code: String,
    },
    /// Контакт.
    #[serde(rename = "contact")]
    Contact(ContactData),
    /// Inline‑клавиатура.
    #[serde(rename = "inline_keyboard")]
    InlineKeyboard(InlineKeyboard),
    /// Геолокация.
    #[serde(rename = "location")]
    Location {
        /// Широта.
        latitude: f64,
        /// Долгота.
        longitude: f64,
    },
    /// Поделиться ссылкой.
    #[serde(rename = "share")]
    Share(ShareData),
}

impl Attachment {
    /// Возвращает строковый тип вложения, как того требует API MAX.
    ///
    /// # Пример
    /// ```
    /// use maxbot::Attachment;
    ///
    /// let att = Attachment::sticker("sticker_code");
    /// assert_eq!(att.get_type(), "sticker");
    /// ```
    pub fn get_type(&self) -> &'static str {
        match self {
            Attachment::Image { .. }      => "image",
            Attachment::Video { .. }      => "video",
            Attachment::Audio { .. }      => "audio",
            Attachment::File { .. }       => "file",
            Attachment::Sticker { .. }    => "sticker",
            Attachment::Contact(_)        => "contact",
            Attachment::InlineKeyboard(_) => "inline_keyboard",
            Attachment::Location { .. }   => "location",
            Attachment::Share(_)          => "share",
        }
    }

    // ----- Конструкторы для файловых вложений -----

    /// Создаёт вложение‑изображение из локального файла.
    ///
    /// Файл будет загружен на сервер MAX при отправке сообщения.
    pub fn image_local(path: impl Into<PathBuf>) -> Self {
        Attachment::Image {
            source: AttachmentSource::LocalFile(path.into()),
            token: None,
            url: None,
        }
    }

    /// Создаёт вложение‑изображение по существующему токену.
    pub fn image_token(token: impl Into<String>) -> Self {
        Attachment::Image {
            source: AttachmentSource::Token(token.into()),
            token: None,
            url: None,
        }
    }

    /// Создаёт вложение‑изображение по URL.
    pub fn image_url(url: impl Into<String>) -> Self {
        Attachment::Image {
            source: AttachmentSource::Token(String::new()), // фиктивное значение, не используется
            token: None,
            url: Some(url.into()),
        }
    }

    /// Создаёт вложение‑видео из локального файла.
    pub fn video_local(path: impl Into<PathBuf>) -> Self {
        Attachment::Video {
            source: AttachmentSource::LocalFile(path.into()),
            token: None,
            url: None,
            filename: None,
        }
    }

    /// Создаёт вложение‑видео по существующему токену.
    pub fn video_token(token: impl Into<String>) -> Self {
        Attachment::Video {
            source: AttachmentSource::Token(token.into()),
            token: None,
            url: None,
            filename: None,
        }
    }

    /// Создаёт вложение‑видео по URL.
    pub fn video_url(url: impl Into<String>, filename: impl Into<String>) -> Self {
        Attachment::Video {
            source: AttachmentSource::Token(String::new()),
            token: None,
            url: Some(url.into()),
            filename: Some(filename.into()),
        }
    }

    /// Создаёт вложение‑аудио из локального файла.
    pub fn audio_local(path: impl Into<PathBuf>) -> Self {
        Attachment::Audio {
            source: AttachmentSource::LocalFile(path.into()),
            token: None,
            url: None,
            filename: None,
        }
    }

    /// Создаёт вложение‑аудио по существующему токену.
    pub fn audio_token(token: impl Into<String>) -> Self {
        Attachment::Audio {
            source: AttachmentSource::Token(token.into()),
            token: None,
            url: None,
            filename: None,
        }
    }

    /// Создаёт вложение‑аудио по URL.
    pub fn audio_url(url: impl Into<String>, filename: impl Into<String>) -> Self {
        Attachment::Audio {
            source: AttachmentSource::Token(String::new()),
            token: None,
            url: Some(url.into()),
            filename: Some(filename.into()),
        }
    }

    /// Создаёт вложение‑файл (документ) из локального файла.
    pub fn file_local(path: impl Into<PathBuf>) -> Self {
        Attachment::File {
            source: AttachmentSource::LocalFile(path.into()),
            token: None,
            url: None,
            filename: None,
        }
    }

    /// Создаёт вложение‑файл по существующему токену.
    pub fn file_token(token: impl Into<String>) -> Self {
        Attachment::File {
            source: AttachmentSource::Token(token.into()),
            token: None,
            url: None,
            filename: None,
        }
    }

    /// Создаёт вложение‑файл по прямому URL.
    pub fn file_url(url: impl Into<String>, filename: impl Into<String>) -> Self {
        Attachment::File {
            source: AttachmentSource::Token(String::new()),
            token: None,
            url: Some(url.into()),
            filename: Some(filename.into()),
        }
    }

    // ----- Конструкторы для других типов вложений -----

    /// Создаёт вложение‑стикер по его коду.
    pub fn sticker(code: impl Into<String>) -> Self {
        Attachment::Sticker { code: code.into() }
    }

    /// Создаёт вложение‑контакт с указанными данными.
    pub fn contact(data: ContactData) -> Self {
        Attachment::Contact(data)
    }

    /// Создаёт вложение‑inline‑клавиатуру.
    pub fn inline_keyboard(keyboard: InlineKeyboard) -> Self {
        Attachment::InlineKeyboard(keyboard)
    }

    /// Создаёт вложение‑геолокацию.
    pub fn location(latitude: f64, longitude: f64) -> Self {
        Attachment::Location { latitude, longitude }
    }

    /// Создаёт вложение, чтобы поделиться ссылкой.
    pub fn share(data: ShareData) -> Self {
        Attachment::Share(data)
    }
}

/// Построитель inline‑клавиатуры.
///
/// Позволяет удобно формировать двумерный массив кнопок.
///
/// # Пример
/// ```
/// use maxbot::{InlineKeyboardBuilder, InlineKeyboardButton};
///
/// let keyboard = InlineKeyboardBuilder::new()
///     .row(vec![
///         InlineKeyboardButton::callback("Кнопка 1", "payload1"),
///         InlineKeyboardButton::link("Сайт", "https://example.com"),
///     ])
///     .button(InlineKeyboardButton::request_contact("Отправить контакт"))
///     .build();
/// ```
pub struct InlineKeyboardBuilder {
    rows: Vec<Vec<InlineKeyboardButton>>,
}

impl InlineKeyboardBuilder {
    /// Создаёт новый пустой построитель.
    pub fn new() -> Self {
        Self { rows: vec![] }
    }

    /// Добавляет строку кнопок.
    pub fn row(mut self, buttons: Vec<InlineKeyboardButton>) -> Self {
        self.rows.push(buttons);
        self
    }

    /// Добавляет одну кнопку в последнюю строку.
    /// Если строк ещё нет, создаёт новую строку.
    pub fn button(mut self, btn: InlineKeyboardButton) -> Self {
        if self.rows.is_empty() {
            self.rows.push(vec![]);
        }
        self.rows.last_mut().unwrap().push(btn);
        self
    }

    /// Добавляет несколько кнопок в последнюю строку.
    pub fn buttons(mut self, btns: Vec<InlineKeyboardButton>) -> Self {
        if self.rows.is_empty() {
            self.rows.push(vec![]);
        }
        self.rows.last_mut().unwrap().extend(btns);
        self
    }

    /// Строит готовую [`InlineKeyboard`].
    pub fn build(self) -> InlineKeyboard {
        InlineKeyboard { buttons: self.rows }
    }
}

impl Default for InlineKeyboardBuilder {
    fn default() -> Self {
        Self::new()
    }
}

// ----- Конструкторы кнопок -----

impl InlineKeyboardButton {
    /// Создаёт кнопку типа `"callback"`.
    ///
    /// При нажатии бот получит `callback_query` с указанным `payload`.
    pub fn callback(text: impl Into<String>, payload: impl Into<String>) -> Self {
        Self {
            r#type: "callback".to_string(),
            text: text.into(),
            payload: Some(payload.into()),
            url: None,
            quick: None,
            web_app: None,
            contact_id: None,
            app_payload: None,
            message_text: None,
        }
    }

    /// Создаёт кнопку‑ссылку типа `"link"`.
    pub fn link(text: impl Into<String>, url: impl Into<String>) -> Self {
        Self {
            r#type: "link".to_string(),
            text: text.into(),
            payload: None,
            url: Some(url.into()),
            quick: None,
            web_app: None,
            contact_id: None,
            app_payload: None,
            message_text: None,
        }
    }

    /// Создаёт кнопку запроса контакта у пользователя.
    pub fn request_contact(text: impl Into<String>) -> Self {
        Self {
            r#type: "request_contact".to_string(),
            text: text.into(),
            payload: None,
            url: None,
            quick: Some(true),
            web_app: None,
            contact_id: None,
            app_payload: None,
            message_text: None,
        }
    }

    /// Создаёт кнопку запроса геолокации у пользователя.
    pub fn request_location(text: impl Into<String>) -> Self {
        Self {
            r#type: "request_geo_location".to_string(),
            text: text.into(),
            payload: None,
            url: None,
            quick: Some(true),
            web_app: None,
            contact_id: None,
            app_payload: None,
            message_text: None,
        }
    }

    /// Создаёт кнопку открытия веб‑приложения (тип `"open_app"`).
    pub fn open_app(
        text: impl Into<String>,
        web_app_url: impl Into<String>,
        contact_id: i64,
        payload: Option<String>,
    ) -> Self {
        Self {
            r#type: "open_app".to_string(),
            text: text.into(),
            payload,
            url: None,
            quick: None,
            web_app: Some(web_app_url.into()),
            contact_id: Some(contact_id),
            app_payload: None,
            message_text: None,
        }
    }

    /// Создаёт кнопку отправки сообщения от имени пользователя (тип `"message"`).
    pub fn message(text: impl Into<String>, message_text: impl Into<String>) -> Self {
        Self {
            r#type: "message".to_string(),
            text: text.into(),
            payload: None,
            url: None,
            quick: None,
            web_app: None,
            contact_id: None,
            app_payload: None,
            message_text: Some(message_text.into()),
        }
    }

    /// Создаёт кнопку типа `"clipboard"`.
    ///
    /// При нажатии текст `payload` копируется в буфер обмена пользователя.
    /// Эта кнопка не отправляет callback-запрос боту.
    pub fn clipboard(text: impl Into<String>, payload: impl Into<String>) -> Self {
        Self {
            r#type: "clipboard".to_string(),
            text: text.into(),
            payload: Some(payload.into()),
            url: None,
            quick: None,
            web_app: None,
            contact_id: None,
            app_payload: None,
            message_text: None,
        }
    }
}
