use poise::{
    serenity_prelude::{model::id::ChannelId, CreateEmbed, CreateEmbedFooter, PartialChannel},
    CreateReply,
};
use serde::{Deserialize, Serialize};
use serde_repr::*;

use crate::{
    component_models::pager::{LookPager, Pager},
    consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
    models::{reminder::Reminder, CtxData},
    utils::{Extract, Recordable},
    Context, Error,
};

#[derive(Serialize_repr, Deserialize_repr, Copy, Clone)]
#[repr(u8)]
pub enum TimeDisplayType {
    Absolute = 0,
    Relative = 1,
}

#[derive(Serialize, Deserialize, Copy, Clone)]
pub struct LookFlags {
    pub show_disabled: bool,
    pub channel_id: Option<ChannelId>,
    pub time_display: TimeDisplayType,
}

impl Default for LookFlags {
    fn default() -> Self {
        Self { show_disabled: true, channel_id: None, time_display: TimeDisplayType::Relative }
    }
}

#[derive(Serialize, Deserialize, Extract)]
pub struct Options {
    channel: Option<PartialChannel>,
    disabled: Option<bool>,
    relative: Option<bool>,
}

impl Recordable for Options {
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
        let timezone = ctx.timezone().await;

        let flags = LookFlags {
            show_disabled: self.disabled.unwrap_or(true),
            channel_id: self.channel.map(|c| c.id),
            time_display: self.relative.map_or(TimeDisplayType::Relative, |b| {
                if b {
                    TimeDisplayType::Relative
                } else {
                    TimeDisplayType::Absolute
                }
            }),
        };

        let channel_id = if let Some(channel) = ctx.cache().channel(ctx.channel_id()) {
            if Some(channel.guild_id) == ctx.guild_id() {
                flags.channel_id.unwrap_or_else(|| ctx.channel_id())
            } else {
                ctx.channel_id()
            }
        } else {
            ctx.channel_id()
        };

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

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

        if reminders.is_empty() {
            let _ = ctx.say("No reminders on specified channel").await;
        } else {
            let mut char_count = 0;

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

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

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

            let pager = LookPager::new(flags, timezone);

            ctx.send(
                CreateReply::default()
                    .ephemeral(true)
                    .embed(
                        CreateEmbed::new()
                            .title(format!(
                                "Reminders{}",
                                channel_name.map_or(String::new(), |n| format!(" on #{}", n))
                            ))
                            .description(display)
                            .footer(CreateEmbedFooter::new(format!("Page {} of {}", 1, pages)))
                            .color(*THEME_COLOR),
                    )
                    .components(vec![pager.create_button_row(pages)]),
            )
            .await?;
        }

        Ok(())
    }
}

/// View reminders on a specific channel
#[poise::command(
    slash_command,
    rename = "look",
    identifying_name = "look",
    default_member_permissions = "MANAGE_GUILD"
)]
pub async fn command(
    ctx: Context<'_>,
    #[description = "Channel to view reminders on"] channel: Option<PartialChannel>,
    #[description = "Whether to show disabled reminders or not"] disabled: Option<bool>,
    #[description = "Whether to display times as relative or exact times"] relative: Option<bool>,
) -> Result<(), Error> {
    (Options { channel, disabled, relative }).run(ctx).await
}
