pub(crate) mod pager;

use std::io::Cursor;

use base64::{engine::general_purpose, Engine};
use chrono_tz::Tz;
use log::warn;
use poise::{
    serenity_prelude as serenity,
    serenity_prelude::{
        builder::CreateEmbed, ComponentInteraction, ComponentInteractionDataKind, Context,
        CreateEmbedFooter, CreateInteractionResponse, CreateInteractionResponseMessage,
    },
};
use rmp_serde::Serializer;
use serde::{Deserialize, Serialize};

use crate::{
    commands::{
        command_macro::list_macro::{max_macro_page, show_macro_page},
        delete::{max_delete_page, show_delete_page},
        todo::{max_todo_page, show_todo_page},
    },
    component_models::pager::{DelPager, LookPager, MacroPager, Pager, TodoPager},
    consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
    models::reminder::Reminder,
    utils::reply_to_interaction_response_message,
    Data,
};

#[derive(Deserialize, Serialize)]
#[serde(tag = "type")]
#[repr(u8)]
pub enum ComponentDataModel {
    LookPager(LookPager),
    DelPager(DelPager),
    TodoPager(TodoPager),
    DelSelector(DelSelector),
    TodoSelector(TodoSelector),
    MacroPager(MacroPager),
    UndoReminder(UndoReminder),
}

impl ComponentDataModel {
    pub fn to_custom_id(&self) -> String {
        let mut buf = Vec::new();
        self.serialize(&mut Serializer::new(&mut buf)).unwrap();
        general_purpose::STANDARD.encode(buf)
    }

    pub fn from_custom_id(data: &String) -> Self {
        let buf = general_purpose::STANDARD
            .decode(data)
            .map_err(|e| format!("Could not decode `custom_id' {}: {:?}", data, e))
            .unwrap();
        let cur = Cursor::new(buf);
        rmp_serde::from_read(cur).unwrap()
    }

    pub async fn act(&self, ctx: &Context, data: &Data, component: &ComponentInteraction) {
        match self {
            ComponentDataModel::LookPager(pager) => {
                let flags = pager.flags;

                let channel_id = {
                    let channel_opt = ctx.cache.channel(component.channel_id);

                    if let Some(channel) = channel_opt {
                        if Some(channel.guild_id) == component.guild_id {
                            flags.channel_id.unwrap_or(component.channel_id)
                        } else {
                            component.channel_id
                        }
                    } else {
                        component.channel_id
                    }
                };

                let reminders = Reminder::from_channel(&data.database, channel_id, &flags).await;

                let pages = reminders
                    .iter()
                    .map(|reminder| reminder.display(&flags, &pager.timezone))
                    .fold(0, |t, r| t + r.len())
                    .div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);

                let channel_name =
                    ctx.cache.channel(channel_id).map(|channel| channel.name.clone());

                let next_page = pager.next_page(pages);

                let mut char_count = 0;
                let mut skip_char_count = 0;

                let display = reminders
                    .iter()
                    .map(|reminder| reminder.display(&flags, &pager.timezone))
                    .skip_while(|p| {
                        skip_char_count += p.len();

                        skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * next_page
                    })
                    .take_while(|p| {
                        char_count += p.len();

                        char_count < EMBED_DESCRIPTION_MAX_LENGTH
                    })
                    .collect::<Vec<String>>()
                    .join("");

                let embed = CreateEmbed::default()
                    .title(format!(
                        "Reminders{}",
                        channel_name.map_or(String::new(), |n| format!(" on #{}", n))
                    ))
                    .description(display)
                    .footer(CreateEmbedFooter::new(format!("Page {} of {}", next_page + 1, pages)))
                    .color(*THEME_COLOR);

                let _ = component
                    .create_response(
                        &ctx,
                        CreateInteractionResponse::UpdateMessage(
                            CreateInteractionResponseMessage::new()
                                .embed(embed)
                                .components(vec![pager.create_button_row(pages)]),
                        ),
                    )
                    .await;
            }
            ComponentDataModel::DelPager(pager) => {
                let reminders = Reminder::from_guild(
                    &ctx,
                    &data.database,
                    component.guild_id,
                    component.user.id,
                )
                .await;

                let max_pages = max_delete_page(&reminders, &pager.timezone);

                let resp = show_delete_page(&reminders, pager.next_page(max_pages), pager.timezone);

                let _ = component
                    .create_response(
                        &ctx,
                        CreateInteractionResponse::UpdateMessage(
                            reply_to_interaction_response_message(resp),
                        ),
                    )
                    .await;
            }
            ComponentDataModel::DelSelector(selector) => {
                if let ComponentInteractionDataKind::StringSelect { values } = &component.data.kind
                {
                    let placeholder = vec!["?"; values.len()].join(",");
                    let sql = format!(
                        "UPDATE reminders SET `status` = 'deleted' WHERE id IN ({placeholder})"
                    );

                    let mut query = sqlx::query(&sql);
                    for id in values {
                        query = query.bind(id);
                    }

                    query.execute(&data.database).await.unwrap();

                    let reminders = Reminder::from_guild(
                        &ctx,
                        &data.database,
                        component.guild_id,
                        component.user.id,
                    )
                    .await;

                    let resp = show_delete_page(&reminders, selector.page, selector.timezone);

                    let _ = component
                        .create_response(
                            &ctx,
                            CreateInteractionResponse::UpdateMessage(
                                reply_to_interaction_response_message(resp),
                            ),
                        )
                        .await;
                }
            }
            ComponentDataModel::TodoPager(pager) => {
                if Some(component.user.id.get()) == pager.user_id || pager.user_id.is_none() {
                    let values = if let Some(uid) = pager.user_id {
                        sqlx::query!(
                            "
                            SELECT todos.id, value FROM todos
                            WHERE user_id = ?
                            ",
                            uid,
                        )
                        .fetch_all(&data.database)
                        .await
                        .unwrap()
                        .iter()
                        .map(|row| (row.id as usize, row.value.clone()))
                        .collect::<Vec<(usize, String)>>()
                    } else if let Some(cid) = pager.channel_id {
                        sqlx::query!(
                            "
                            SELECT todos.id, value FROM todos
                            INNER JOIN channels ON todos.channel_id = channels.id
                            WHERE channels.channel = ?
                            ",
                            cid,
                        )
                        .fetch_all(&data.database)
                        .await
                        .unwrap()
                        .iter()
                        .map(|row| (row.id as usize, row.value.clone()))
                        .collect::<Vec<(usize, String)>>()
                    } else {
                        sqlx::query!(
                            "
                            SELECT todos.id, value FROM todos
                            INNER JOIN guilds ON todos.guild_id = guilds.id
                            WHERE guilds.guild = ?
                            ",
                            pager.guild_id,
                        )
                        .fetch_all(&data.database)
                        .await
                        .unwrap()
                        .iter()
                        .map(|row| (row.id as usize, row.value.clone()))
                        .collect::<Vec<(usize, String)>>()
                    };

                    let max_pages = max_todo_page(&values);

                    let resp = show_todo_page(
                        &values,
                        pager.next_page(max_pages),
                        pager.user_id,
                        pager.channel_id,
                        pager.guild_id,
                    );

                    let _ = component
                        .create_response(
                            &ctx,
                            CreateInteractionResponse::UpdateMessage(
                                reply_to_interaction_response_message(resp),
                            ),
                        )
                        .await;
                } else {
                    let _ = component
                        .create_response(
                            &ctx,
                            CreateInteractionResponse::Message(
                                CreateInteractionResponseMessage::new()
                                    .ephemeral(true)
                                    .content("Only the user who performed the command can use these components")
                            )
                        )
                        .await;
                }
            }
            ComponentDataModel::TodoSelector(selector) => {
                if Some(component.user.id.get()) == selector.user_id || selector.user_id.is_none() {
                    if let ComponentInteractionDataKind::StringSelect { values } =
                        &component.data.kind
                    {
                        let placeholder = vec!["?"; values.len()].join(",");
                        let sql = format!("DELETE FROM todos WHERE id IN ({placeholder})");

                        let mut query = sqlx::query(&sql);
                        for id in values {
                            query = query.bind(id);
                        }

                        query.execute(&data.database).await.unwrap();

                        let values = if let Some(uid) = selector.user_id {
                            sqlx::query!(
                                "
                                SELECT todos.id, value FROM todos
                                WHERE user_id = ?
                                ",
                                uid,
                            )
                            .fetch_all(&data.database)
                            .await
                            .unwrap()
                            .iter()
                            .map(|row| (row.id as usize, row.value.clone()))
                            .collect::<Vec<(usize, String)>>()
                        } else if let Some(cid) = selector.channel_id {
                            sqlx::query!(
                                "
                            SELECT todos.id, value FROM todos
                            INNER JOIN channels ON todos.channel_id = channels.id
                            WHERE channels.channel = ?
                            ",
                                cid,
                            )
                            .fetch_all(&data.database)
                            .await
                            .unwrap()
                            .iter()
                            .map(|row| (row.id as usize, row.value.clone()))
                            .collect::<Vec<(usize, String)>>()
                        } else {
                            sqlx::query!(
                                "
                            SELECT todos.id, value FROM todos
                            INNER JOIN guilds ON todos.guild_id = guilds.id
                            WHERE guilds.guild = ?
                            ",
                                selector.guild_id,
                            )
                            .fetch_all(&data.database)
                            .await
                            .unwrap()
                            .iter()
                            .map(|row| (row.id as usize, row.value.clone()))
                            .collect::<Vec<(usize, String)>>()
                        };

                        let resp = show_todo_page(
                            &values,
                            selector.page,
                            selector.user_id,
                            selector.channel_id,
                            selector.guild_id,
                        );

                        let _ = component
                            .create_response(
                                &ctx,
                                CreateInteractionResponse::UpdateMessage(
                                    reply_to_interaction_response_message(resp),
                                ),
                            )
                            .await;
                    }
                } else {
                    let _ = component
                        .create_response(
                            &ctx,
                            CreateInteractionResponse::Message(
                                CreateInteractionResponseMessage::new()
                                    .ephemeral(true)
                                    .content("Only the user who performed the command can use these components")
                            )
                        )
                        .await;
                }
            }
            ComponentDataModel::MacroPager(pager) => {
                let macros = data.command_macros(component.guild_id.unwrap()).await.unwrap();

                let max_page = max_macro_page(&macros);
                let page = pager.next_page(max_page);

                let resp = show_macro_page(&macros, page);

                let _ = component
                    .create_response(
                        &ctx,
                        CreateInteractionResponse::UpdateMessage(
                            reply_to_interaction_response_message(resp),
                        ),
                    )
                    .await;
            }
            ComponentDataModel::UndoReminder(undo_reminder) => {
                if component.user.id == undo_reminder.user_id {
                    let reminder =
                        Reminder::from_id(&data.database, undo_reminder.reminder_id).await;

                    if let Some(reminder) = reminder {
                        match reminder.delete(&data.database).await {
                            Ok(()) => {
                                let _ = component
                                    .create_response(
                                        &ctx,
                                        CreateInteractionResponse::UpdateMessage(
                                            CreateInteractionResponseMessage::new().embed(
                                                CreateEmbed::new()
                                                    .title("Reminder Canceled")
                                                    .description("This reminder has been canceled.")
                                                    .color(*THEME_COLOR),
                                            ),
                                        ),
                                    )
                                    .await;
                            }
                            Err(e) => {
                                warn!("Error canceling reminder: {:?}", e);

                                let _ = component
                                    .create_response(
                                        &ctx,
                                        CreateInteractionResponse::Message(
                                            CreateInteractionResponseMessage::new().content(
                                                "An error occurred trying to cancel this reminder.",
                                            ).ephemeral(true),
                                        ),
                                    )
                                    .await;
                            }
                        }
                    } else {
                        let _ = component
                            .create_response(
                                &ctx,
                                CreateInteractionResponse::Message(
                                    CreateInteractionResponseMessage::new().content(
                                        "The reminder could not be canceled. It may have already been deleted.",
                                    ).ephemeral(true),
                                ),
                            )
                            .await;
                    }
                } else {
                    let _ = component
                        .create_response(
                            &ctx,
                            CreateInteractionResponse::Message(
                                CreateInteractionResponseMessage::new()
                                    .content("Only the user who performed the command can use these components")
                                    .ephemeral(true),
                            ),
                        )
                        .await;
                }
            }
        }
    }
}

#[derive(Serialize, Deserialize)]
pub struct DelSelector {
    pub page: usize,
    pub timezone: Tz,
}

#[derive(Serialize, Deserialize)]
pub struct TodoSelector {
    pub page: usize,
    pub user_id: Option<u64>,
    pub channel_id: Option<u64>,
    pub guild_id: Option<u64>,
}

#[derive(Serialize, Deserialize)]
pub struct UndoReminder {
    pub user_id: serenity::UserId,
    pub reminder_id: u32,
}
