use std::fmt::Display;
use teloxide::types::{
    CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton,
    KeyboardMarkup, Message,
};
use crate::ui::prelude::*;
use rust_shared::repository::model::PoolType;

pub const PROMOS_PER_PAGE: usize = 10;

pub fn make_keyboard(buttons: Vec<&str>, row_size: usize) -> KeyboardMarkup {
    let mut keyboard: Vec<Vec<KeyboardButton>> = vec![vec![]];
    for buttons in buttons.chunks(row_size) {
        let row = buttons
            .iter()
            .map(|&button| KeyboardButton::new(button))
            .collect();

        keyboard.push(row);
    }
    KeyboardMarkup::new(keyboard).resize_keyboard()
}

pub fn make_keyboard_from_vec(buttons: Vec<Vec<&str>>) -> KeyboardMarkup {
    let mut keyboard: Vec<Vec<KeyboardButton>> = vec![vec![]];
    for buttons in buttons.iter() {
        let row = buttons
            .iter()
            .map(|&button| KeyboardButton::new(button))
            .collect();

        keyboard.push(row);
    }
    KeyboardMarkup::new(keyboard).resize_keyboard()
}

pub fn make_inline_keyboard<T: Display>(
    buttons: Vec<InlineButton<T>>,
    row_size: usize,
) -> InlineKeyboardMarkup {
    let mut keyboard: Vec<Vec<InlineKeyboardButton>> = vec![vec![]];
    for buttons in buttons.chunks(row_size) {
        let row = buttons
            .iter()
            .map(|button| {
                InlineKeyboardButton::callback(
                    button.text.clone(),
                    button.callback.to_string(),
                )
            })
            .collect();

        keyboard.push(row);
    }
    InlineKeyboardMarkup::new(keyboard)
}

pub struct InlineButton<T> {
    text: String,
    callback: T,
}

impl<T> From<(String, T)> for InlineButton<T> {
    fn from((text, callback): (String, T)) -> InlineButton<T> {
        InlineButton { text, callback }
    }
}

impl<T> From<(&'static str, T)> for InlineButton<T> {
    fn from((text, callback): (&'static str, T)) -> InlineButton<T> {
        InlineButton {
            text: text.to_string(),
            callback,
        }
    }
}

pub fn get_callback_message(q: CallbackQuery) -> Option<Message> {
    if let Some(message) = q.message {
        message.regular_message().cloned()
    } else {
        log::error!("Message does not exist");
        None
    }
}

/// Pool selection inline keyboard
/// 
/// # Arguments
/// * `callback_fn` - Function that generates callback for each pool type
/// * `back_callback` - Callback для кнопки "Назад" (опционально)
/// * `included_pools` - Pools to include in selection (if empty, includes all pools)
pub fn make_pool_selection_keyboard<T: Display, F>(
    callback_fn: F,
    back_callback: Option<T>,
    ln: Language,
    included_pools: &[PoolType],
) -> InlineKeyboardMarkup 
where
    F: Fn(PoolType) -> T,
{
    let available_pools = included_pools.to_vec();
    
    let pool_buttons: Vec<InlineButton<T>> = available_pools
        .into_iter()
        .map(|pool_type| {
            let display_name = pool_type.display_name_custom(&ln, true);
            (display_name, callback_fn(pool_type)).into()
        })
        .collect();

    let mut keyboard = make_inline_keyboard(pool_buttons, 2);
    
    if let Some(back_cb) = back_callback {
        let back_button = InlineKeyboardButton::callback(
            t8x!(Ru, "🔙Назад🔙", &ln),
            back_cb.to_string(),
        );
        keyboard = keyboard.append_row(vec![back_button]);
    }
    
    keyboard
}

/// Pool selection reply keyboard
/// 
/// # Arguments
/// * `include_back` - Включать ли кнопку "Назад"
pub fn make_pool_selection_reply_keyboard(include_back: bool, ln: Language) -> KeyboardMarkup {
    let mut keyboard: Vec<Vec<KeyboardButton>> = vec![
        vec![
            KeyboardButton::new(t8x!(Ru, "Серверный", &ln)),
            KeyboardButton::new(t8x!(Ru, "Резидент", &ln)),
        ],
        vec![
            KeyboardButton::new(t8x!(Ru, "Резидент Премиум", &ln)),
            KeyboardButton::new(t8x!(Ru, "Мобильный", &ln)),
        ],
    ];
    
    if include_back {
        keyboard.push(vec![KeyboardButton::new(t8x!(Ru, "🔙Назад в меню🔙", &ln))]);
    }
    
    KeyboardMarkup::new(keyboard).resize_keyboard()
}

pub struct SelectPoolHandlerSettings {
    included_pools: Vec<PoolType>,
    message: String,
    handler: HandlerSettings,
}

impl From<HandlerSettings> for SelectPoolHandlerSettings {
    fn from(handler_settings: HandlerSettings) -> Self {
        Self {
            included_pools: PoolType::all(),
            message: t8x!("<b>Select a pool:</b>", &handler_settings.language).to_string(),
            handler: handler_settings,
        }
    }
}

impl SelectPoolHandlerSettings {
    pub fn include_pools<I, T>(mut self, pools: I) -> Self
    where
        I: IntoIterator<Item = T>,
        T: Into<PoolType>,
    {
        self.included_pools = pools.into_iter().map(Into::into).collect();
        self
    }

    pub fn with_message(mut self, message: impl Into<String>) -> Self {
        self.message = message.into();
        self
    }
}

/// Pool selection handler - shows all pools by default
/// 
/// # Arguments
/// * `bot` - Bot instance
/// * `msg` - Message to respond to  
/// * `settings` - Handler settings (language, behavior, etc.)
/// * `callback_fn` - Function that generates callback for each pool type
/// * `back_callback` - Optional callback for back button
pub async fn select_pool_handler_constructor(bot: Bot, msg: Message, callback_fn: impl Fn(PoolType) -> String, back_callback: Option<String>, settings: SelectPoolHandlerSettings) -> HandlerResult {
    let keyboard = make_pool_selection_keyboard(
        callback_fn,
        back_callback,
        settings.handler.language,
        &settings.included_pools
    );

    match settings.handler.behavior {
        ForceHandlerBehavior::EditMessage => {
            bot.edit_message_text(msg.chat.id, msg.id, settings.message)
                .reply_markup(keyboard)
                .await?;
        }
        ForceHandlerBehavior::DeleteMessage => {
            bot.delete_message(msg.chat.id, msg.id).await?;
            bot.send_message(msg.chat.id, settings.message)
                .reply_markup(keyboard)
                .await?;
        }
        _ => {
            bot.send_message(msg.chat.id, settings.message)
                .reply_markup(keyboard)
                .await?;
        }
    }

    Ok(())
}

pub async fn create_traffic_promo_help_message(
    bot: Bot,
    msg: Message,
) -> HandlerResult {
    let create_traffic_promo_help_message = indoc!(
            "
            ❓ Использование команды <code>/promo</code>:
            
            <code>/promo &ltназвания,&gt &ltактивации&gt &ltгб&gt &ltколичество&gt [дата_истечения]</code>
            
            <b>Параметры:</b>
            • <b>названия</b> - промокоды через запятую (например: BLACK2024,FRIDAY2024)
                 Используйте <code>-</code> для автоматической генерации названий
            • <b>активации</b> - максимальное количество использований на промокод
            • <b>гб</b> - количество гигабайт трафика
            • <b>количество</b> - сколько промокодов создать
            • <b>дата_истечения</b> - дата истечения в формате YYYY-MM-DD (необязательно)
            
            <b>Пример:</b>
            <code>/promo BLACK2024,FRIDAY2024 1 5 10 2025-12-31</code>
            <code>/promo BLACK2024,FRIDAY2024 6 3 8</code>
            <code>/promo - 1 5 10</code> - создать 10 промокодов с автогенерацией названий
            "
        );

    bot.send_message(msg.chat.id, create_traffic_promo_help_message)
        .await?;
    Ok(())
}

/// Configuration for promo pool selection
pub struct PromoPoolConfig<T> {
    pub buttons: Vec<InlineButton<T>>,
    pub back_callback: T,
    pub state_function: fn(Vec<String>, i32, i32, i32, Option<String>) -> AdminStateKind,
}

pub fn admin_promo_config() -> PromoPoolConfig<AdminCallback> {
    PromoPoolConfig {
        buttons: vec![
            ("🖥 Residential", AdminCallback::PromoSelectPool {
                pool: PoolType::Residential,
            }).into(),
            ("🖥 Residential Premium", AdminCallback::PromoSelectPool {
                pool: PoolType::ResidentialPremium,
            }).into(),
            ("📱 Mobile", AdminCallback::PromoSelectPool {
                pool: PoolType::Mobile,
            }).into(),
            ("📺 Datacenter", AdminCallback::PromoSelectPool {
                pool: PoolType::Datacenter,
            }).into(),
            ("🔙 Отмена", AdminCallback::AdminMenu).into(),
        ],
        back_callback: AdminCallback::AdminMenu,
        state_function: |names, activations, gb_amount, count, expiration_date| {
            AdminStateKind::PromoSelectPool {
                names,
                activations,
                gb_amount,
                count,
                expiration_date,
            }
        },
    }
}

/// Create support promo configuration
pub fn support_promo_config() -> PromoPoolConfig<SupportCallback> {
    PromoPoolConfig {
        buttons: vec![
            ("🖥 Residential", SupportCallback::PromoSelectPool {
                pool: PoolType::Residential,
            }).into(),
            ("🖥 Residential Premium", SupportCallback::PromoSelectPool {
                pool: PoolType::ResidentialPremium,
            }).into(),
            ("📱 Mobile", SupportCallback::PromoSelectPool {
                pool: PoolType::Mobile,
            }).into(),
            ("📺 Datacenter", SupportCallback::PromoSelectPool {
                pool: PoolType::Datacenter,
            }).into(),
            ("🔙 Отмена", SupportCallback::AdminMenu).into(),
        ],
        back_callback: SupportCallback::AdminMenu,
        state_function: |names, activations, gb_amount, count, expiration_date| {
            AdminStateKind::PromoSelectPool {
                names,
                activations,
                gb_amount,
                count,
                expiration_date,
            }
        },
    }
}


/// Parse command arguments for promo creation
fn parse_promo_args(text: &str) -> Result<(Vec<String>, i32, i32, i32, Option<String>), String> {
    let args: Vec<&str> = text.split_whitespace().collect();

    if args.len() < 5 {
        return Err("Insufficient arguments".to_string());
    }

    let names: Vec<String> = if args[1] == "-" {
        Vec::new()
    } else {
        args[1].split(',').map(|s| s.trim().to_string()).collect()
    };

    let activations = args[2].parse::<i32>()
        .map_err(|_| "Invalid activations number".to_string())?;
    if activations <= 0 {
        return Err("Activations must be positive".to_string());
    }

    let gb_amount = args[3].parse::<i32>()
        .map_err(|_| "Invalid GB amount".to_string())?;
    if gb_amount <= 0 {
        return Err("GB amount must be positive".to_string());
    }

    let count = args[4].parse::<i32>()
        .map_err(|_| "Invalid count number".to_string())?;
    if count <= 0 {
        return Err("Count must be positive".to_string());
    }

    let expiration_date = if args.len() > 5 {
        let date_str = args[5];
        if date_str.len() == 10 && date_str.matches('-').count() == 2 {
            Some(date_str.to_string())
        } else {
            return Err("Invalid date format. Use YYYY-MM-DD".to_string());
        }
    } else {
        None
    };

    Ok((names, activations, gb_amount, count, expiration_date))
}

/// Create promo selection message with parsed data
fn create_promo_selection_message(
    names: &[String],
    activations: i32,
    gb_amount: i32,
    count: i32,
    expiration_date: &Option<String>,
) -> String {
    let names_display = if names.is_empty() {
        "Автогенерация".to_string()
    } else {
        names.join(", ")
    };

    format!(
        indoc!(
            "
            📝 <b>Создание промокодов на трафик</b>
            
            <b>Названия:</b> {}
            <b>Активации на промокод:</b> {}
            <b>Гигабайт на активацию:</b> {}
            <b>Количество промокодов:</b> {}
            <b>Дата истечения:</b> {}
            
            Выберите пул для промокодов:
            "
        ),
        names_display,
        activations,
        gb_amount,
        count,
        expiration_date.as_deref().unwrap_or("Не указана")
    )
}

/// Generic promo handler with configuration
pub async fn promo_handler_with_config<T: Display>(
    bot: Bot,
    msg: Message,
    dialogue: MasterDialogue,
    config: PromoPoolConfig<T>,
) -> HandlerResult {
    let Some(text) = msg.text() else {
        bot.send_message(
            msg.chat.id,
            "❌ Необходимо отправить текстовое сообщение",
        )
        .await?;
        return Ok(());
    };

    match parse_promo_args(text) {
        Ok((names, activations, gb_amount, count, expiration_date)) => {
            let message = create_promo_selection_message(
                &names,
                activations,
                gb_amount,
                count,
                &expiration_date,
            );

            let keyboard = make_inline_keyboard(config.buttons, 2);

            bot.send_message(msg.chat.id, message)
                .reply_markup(keyboard)
                .await?;

            dialogue.update_admin_kind((config.state_function)(names, activations, gb_amount, count, expiration_date))
                .await?;
        }
        Err(_) => {
            create_traffic_promo_help_message(bot, msg).await?;
        }
    }

    Ok(())
}
/// Configuration for promo creation handler
pub enum PromoCreateConfig {
    Admin,
    Support,
}

impl PromoCreateConfig {
    /// Get the back callback for this configuration
    fn back_callback(&self) -> String {
        match self {
            PromoCreateConfig::Admin => AdminCallback::AdminMenu.to_string(),
            PromoCreateConfig::Support => SupportCallback::AdminMenu.to_string(),
        }
    }
}

/// Extract promo data from dialogue state
fn extract_promo_data(
    current_state: Option<MasterState>,
) -> Result<(Vec<String>, i32, i32, i32, Option<String>), String> {
    match current_state {
        Some(MasterState { 
            admin_kind: AdminStateKind::PromoSelectPool {
                names,
                activations,
                gb_amount,
                count,
                expiration_date,
            }, 
            .. 
        }) => Ok((names, activations, gb_amount, count, expiration_date)),
        Some(MasterState { 
            support_kind: SupportStateKind::PromoSelectPool {
                names,
                activations,
                gb_amount,
                count,
                expiration_date,
            }, 
            .. 
        }) => Ok((names, activations, gb_amount, count, expiration_date)),
        _ => Err("Promo data not found in dialogue state".to_string()),
    }
}

/// Generate promo file content and metadata
fn generate_promo_file_data(
    created_promo_names: &[String],
    selected_pool: PoolType,
    activations: i32,
    gb_amount: i32,
    count: i32,
    expiration_date: &Option<String>,
) -> (String, String) {
    let file_content = created_promo_names.join("\n");
    
    let filename = format!(
        "promos_{}_{}_act_{}_gb_{}_count_{}_{}.txt",
        selected_pool,
        if let Some(exp) = expiration_date {
            exp.replace("-", "")
        } else {
            "no_exp".to_string()
        },
        activations,
        gb_amount,
        count,
        chrono::Utc::now().format("%Y%m%d_%H%M%S")
    );
    
    (file_content, filename)
}

/// Create success message for promo creation
fn create_success_message(
    selected_pool: PoolType,
    activations: i32,
    gb_amount: i32,
    created_count: usize,
    expiration_date: &Option<String>,
) -> String {
    format!(
        indoc!(
            "
            ✅ <b>Промокоды успешно созданы!</b>
            
            <b>Пул:</b> {}
            <b>Активации на промокод:</b> {}
            <b>Гигабайт на активацию:</b> {}
            <b>Количество созданных промокодов:</b> {}
            <b>Дата истечения:</b> {}
            
            📁 Файл с промокодами отправлен отдельным сообщением
            "
        ),
        selected_pool.display_name(&Language::Ru),
        activations,
        gb_amount,
        created_count,
        expiration_date.as_deref().unwrap_or("Не указана")
    )
}

/// Create error message for promo creation failure
fn create_error_message(error: &str, names: &[String]) -> String {
    if error.starts_with("CONFLICT:") {
        let names_display = if names.is_empty() {
            "Автогенерация".to_string()
        } else {
            names.join(", ")
        };
        
        format!(
            indoc! {r#"
            ❌ Один или несколько указанных названий промокодов уже существуют.

            💡 Предоставленные названия: {names}
            "#},
            names = names_display
        )
    } else {
        format!("❌ Ошибка при создании промокодов: {}", error)
    }
}

/// Generic promo creation handler
pub async fn promo_create_handler_with_config(
    bot: Bot,
    dialogue: MasterDialogue,
    msg: Message,
    selected_pool: PoolType,
    config: PromoCreateConfig,
) -> HandlerResult {
    let current_state = dialogue.get().await?;
    
    let (names, activations, gb_amount, count, expiration_date) = 
        match extract_promo_data(current_state) {
            Ok(data) => data,
            Err(_) => {
                bot.edit_message_text(
                    msg.chat.id,
                    msg.id,
                    "❌ Ошибка: данные о промокоде не найдены",
                )
                .await?;
                return Ok(());
            }
        };

    bot.edit_message_text(msg.chat.id, msg.id, "⏳ Создание промокодов...")
        .await?;

    match api::create_traffic_promo(
        names.clone(),
        selected_pool.to_string(),
        activations,
        gb_amount,
        count,
        expiration_date.clone(),
        msg.chat.id.0,
    )
    .await
    {
        Ok(created_promo_names) => {
            let (file_content, filename) = generate_promo_file_data(
                &created_promo_names,
                selected_pool,
                activations,
                gb_amount,
                count,
                &expiration_date,
            );

            let success_message = create_success_message(
                selected_pool,
                activations,
                gb_amount,
                created_promo_names.len(),
                &expiration_date,
            );

            let back_callback = config.back_callback();
            
            bot.edit_message_text(msg.chat.id, msg.id, success_message)
                .reply_markup(make_inline_keyboard(
                    vec![("🔙 Вернуться в меню", back_callback).into()],
                    1,
                ))
                .await?;

            let file = teloxide::types::InputFile::memory(file_content.into_bytes())
                .file_name(filename);

            bot.send_document(msg.chat.id, file)
                .caption(format!(
                    "📋 Созданные промокоды:\n• Пул: {}\n• Активации: {}\n• ГБ: {}\n• Количество: {}",
                    selected_pool.display_name(&Language::Ru),
                    activations,
                    gb_amount,
                    created_promo_names.len()
                ))
                .await?;
        }
        Err(e) => {
            let error_message = create_error_message(&e.to_string(), &names);
            let back_callback = config.back_callback();

            bot.edit_message_text(msg.chat.id, msg.id, error_message)
                .reply_markup(make_inline_keyboard(
                    vec![("🔙 Вернуться в меню", back_callback).into()],
                    1,
                ))
                .await?;
        }
    }

    dialogue.update_admin_kind(AdminStateKind::Menu).await?;

    Ok(())
}

pub fn format_promo_list_message(promo_codes: &[PromoCode], page: usize) -> String {
    let mut message =
        format!("🗑️ <b>Удаление промокодов</b> (Страница {})\n\n", page + 1);

    for (i, promo) in promo_codes.iter().enumerate() {
        let expiry = promo.term.as_deref().unwrap_or("Без ограничений");
        message.push_str(&format!(
            "{}. <b>{}</b> ({})\n   └ {} ГБ, {} активаций, до: {}\n   └ создан: {}\n\n",
            page * PROMOS_PER_PAGE + i + 1,
            promo.promo_name,
            promo.pool,
            promo.traffic_amount,
            promo.remaining_activation,
            expiry,
            promo.created_by.clone().unwrap_or("неизвестным".to_string())
        ));
    }

    message.push_str(&format!(
        "🗑️ <b>Удаление промокодов</b> (Страница {})\n\n",
        page + 1
    ));

    message
}

pub fn build_detailed_statistics_message(
    mut stats: rust_shared::repository::model::UserStatistics,
    language: &rust_shared::i18n::Language,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
    let mut detailed_message = String::new();
    
    if !stats.usage.is_empty() {
        for usage in &mut stats.usage {
            let traffic = format_mb(usage.traffic);
            let extra_traffic = format_mb(usage.extra_traffic);

            let usage_line = &strfmt!(
                t8x!(Ru, "• {used_at} | {traffic} (+ {extra_traffic} доп.) | {request} запросов\n", &language),
                used_at => mem::take(&mut usage.used_at),
                traffic => traffic,
                extra_traffic => extra_traffic,
                request => usage.request
            )?;
            
            detailed_message.push_str(&usage_line);
        }
    } else {
        let no_data_message = t8x!(Ru, "📭 Нет данных об использовании за этот период", &language);
        detailed_message.push_str(no_data_message);
    }
    
    Ok(detailed_message)
}