use log::warn;
use rocket::{
    get,
    http::CookieJar,
    patch, post,
    serde::json::{json, Json},
    State,
};
use serenity::{
    client::Context,
    model::id::{ChannelId, GuildId, UserId},
};
use sqlx::{MySql, Pool};

use crate::utils::check_subscription;
use crate::web::{
    check_authorization,
    consts::MIN_INTERVAL,
    guards::transaction::Transaction,
    routes::{
        dashboard::{
            create_database_channel, create_reminder, CreateReminder, GetReminder, PatchReminder,
        },
        JsonResult,
    },
    Database,
};

#[post("/api/guild/<id>/reminders", data = "<reminder>")]
pub async fn create_guild_reminder(
    id: u64,
    reminder: Json<CreateReminder>,
    cookies: &CookieJar<'_>,
    ctx: &State<Context>,
    mut transaction: Transaction<'_>,
) -> JsonResult {
    check_authorization(cookies, ctx.inner(), id).await?;

    let user_id =
        cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten().unwrap();

    match create_reminder(
        ctx.inner(),
        &mut transaction,
        GuildId::new(id),
        UserId::new(user_id),
        reminder.into_inner(),
    )
    .await
    {
        Ok(r) => match transaction.commit().await {
            Ok(_) => Ok(r),
            Err(e) => {
                warn!("Couldn't commit transaction: {:?}", e);
                json_err!("Couldn't commit transaction.")
            }
        },

        Err(e) => Err(e),
    }
}

#[get("/api/guild/<id>/reminders")]
pub async fn get_reminders(
    id: u64,
    cookies: &CookieJar<'_>,
    ctx: &State<Context>,
    pool: &State<Pool<MySql>>,
) -> JsonResult {
    check_authorization(cookies, ctx.inner(), id).await?;

    let channels_res = GuildId::new(id).channels(&ctx.inner()).await;

    match channels_res {
        Ok(channels) => {
            let channels =
                channels.keys().into_iter().map(|k| k.get().to_string()).collect::<Vec<String>>();

            let placeholder = vec!["?"; channels.len()].join(",");
            let sql = format!(
                "
                SELECT
                    reminders.attachment_name,
                    reminders.avatar,
                    channels.channel,
                    reminders.content,
                    reminders.embed_author,
                    reminders.embed_author_url,
                    reminders.embed_color,
                    reminders.embed_description,
                    reminders.embed_footer,
                    reminders.embed_footer_url,
                    reminders.embed_image_url,
                    reminders.embed_thumbnail_url,
                    reminders.embed_title,
                    IFNULL(reminders.embed_fields, '[]') AS embed_fields,
                    reminders.enabled,
                    reminders.expires,
                    reminders.interval_seconds,
                    reminders.interval_days,
                    reminders.interval_months,
                    reminders.name,
                    reminders.restartable,
                    reminders.tts,
                    reminders.uid,
                    reminders.username,
                    reminders.utc_time
                FROM reminders
                INNER JOIN channels
                    ON channels.id = reminders.channel_id
                WHERE `status` = 'pending'
                    AND channels.channel IN ({placeholder})
                "
            );

            let mut query = sqlx::query_as::<Database, GetReminder>(&sql);
            for channel in channels {
                query = query.bind(channel);
            }

            query
                .fetch_all(pool.inner())
                .await
                .map(|reminders| Ok(json!(reminders)))
                .unwrap_or_else(|e| {
                    warn!("Failed to complete SQL query: {:?}", e);

                    json_err!("Could not load reminders")
                })
        }
        Err(e) => {
            warn!("Could not fetch channels from {}: {:?}", id, e);

            Ok(json!([]))
        }
    }
}

#[patch("/api/guild/<id>/reminders", data = "<reminder>")]
pub async fn edit_reminder(
    id: u64,
    reminder: Json<PatchReminder>,
    ctx: &State<Context>,
    mut transaction: Transaction<'_>,
    pool: &State<Pool<Database>>,
    cookies: &CookieJar<'_>,
) -> JsonResult {
    check_authorization(cookies, ctx.inner(), id).await?;

    let mut error = vec![];

    let user_id =
        cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten().unwrap();

    if reminder.message_ok() {
        update_field!(transaction.executor(), error, reminder.[
            content,
            embed_author,
            embed_description,
            embed_footer,
            embed_title,
            embed_fields,
            username
        ]);
    } else {
        error.push("Message exceeds limits.".to_string());
    }

    update_field!(transaction.executor(), error, reminder.[
        attachment,
        attachment_name,
        avatar,
        embed_author_url,
        embed_color,
        embed_footer_url,
        embed_image_url,
        embed_thumbnail_url,
        enabled,
        expires,
        name,
        restartable,
        tts,
        utc_time
    ]);

    if reminder.interval_days.flatten().is_some()
        || reminder.interval_months.flatten().is_some()
        || reminder.interval_seconds.flatten().is_some()
    {
        if check_subscription(
            ctx.inner(),
            transaction.executor(),
            UserId::from(user_id),
            Some(GuildId::from(id)),
        )
        .await
        {
            let new_interval_length = match reminder.interval_days {
                Some(interval) => interval.unwrap_or(0),
                None => sqlx::query!(
                    "SELECT interval_days AS days FROM reminders WHERE uid = ?",
                    reminder.uid
                )
                .fetch_one(transaction.executor())
                .await
                .map_err(|e| {
                    warn!("Error updating reminder interval: {:?}", e);
                    json!({ "reminder": Option::<GetReminder>::None, "errors": vec!["Unknown error"] })
                })?
                .days
                .unwrap_or(0),
            } * 86400 + match reminder.interval_months {
                Some(interval) => interval.unwrap_or(0),
                None => sqlx::query!(
                    "SELECT interval_months AS months FROM reminders WHERE uid = ?",
                    reminder.uid
                )
                .fetch_one(transaction.executor())
                .await
                .map_err(|e| {
                    warn!("Error updating reminder interval: {:?}", e);
                    json!({ "reminder": Option::<GetReminder>::None, "errors": vec!["Unknown error"] })
                })?
                .months
                .unwrap_or(0),
            } * 2592000 + match reminder.interval_seconds {
                Some(interval) => interval.unwrap_or(0),
                None => sqlx::query!(
                    "SELECT interval_seconds AS seconds FROM reminders WHERE uid = ?",
                    reminder.uid
                )
                .fetch_one(transaction.executor())
                .await
                .map_err(|e| {
                    warn!("Error updating reminder interval: {:?}", e);
                    json!({ "reminder": Option::<GetReminder>::None, "errors": vec!["Unknown error"] })
                })?
                .seconds
                .unwrap_or(0),
            };

            if new_interval_length < *MIN_INTERVAL {
                error.push(String::from("New interval is too short."));
            } else {
                update_field!(transaction.executor(), error, reminder.[
                    interval_days,
                    interval_months,
                    interval_seconds
                ]);
            }
        }
    } else {
        sqlx::query!(
            "
            UPDATE reminders
            SET interval_seconds = NULL, interval_days = NULL, interval_months = NULL
            WHERE uid = ?
            ",
            reminder.uid
        )
        .execute(transaction.executor())
        .await
        .map_err(|e| {
            warn!("Error updating reminder interval: {:?}", e);
            json!({ "reminder": Option::<GetReminder>::None, "errors": vec!["Unknown error"] })
        })?;
    }

    if reminder.channel > 0 {
        let channel_guild =
            ctx.cache.channel(ChannelId::new(reminder.channel)).map(|channel| channel.guild_id);
        match channel_guild {
            Some(channel_guild) => {
                let channel_matches_guild = channel_guild.get() == id;

                if !channel_matches_guild {
                    warn!(
                        "Error in `edit_reminder`: channel {:?} not found for guild {}",
                        reminder.channel, id
                    );

                    error.push("Channel not found".to_string());
                } else {
                    let channel = create_database_channel(
                        ctx.inner(),
                        ChannelId::new(reminder.channel),
                        &mut transaction,
                    )
                    .await;

                    match channel {
                        Ok(channel) => {
                            match sqlx::query!(
                                "UPDATE reminders SET channel_id = ? WHERE uid = ?",
                                channel,
                                reminder.uid
                            )
                            .execute(transaction.executor())
                            .await
                            {
                                Ok(_) => {}
                                Err(e) => {
                                    warn!("Error setting channel: {:?}", e);

                                    error.push("Couldn't set channel".to_string())
                                }
                            }
                        }

                        Err(e) => {
                            warn!("`create_database_channel` returned an error code: {:?}", e);

                            // Provide more specific error messages based on the error type
                            match e {
                                crate::web::Error::MissingDiscordPermission(permission) => {
                                    error.push(format!("Please ensure the bot has the \"{}\" permission in the channel", permission));
                                }
                                _ => {
                                    error.push("Failed to configure channel for reminders. Please check the bot permissions".to_string());
                                }
                            }
                        }
                    }
                }
            }

            None => {
                warn!(
                    "Error in `edit_reminder`: channel {:?} not found for guild {}",
                    reminder.channel, id
                );

                error.push("Channel not found".to_string());
            }
        }
    }

    if let Err(e) = transaction.commit().await {
        warn!("Couldn't commit transaction: {:?}", e);
        return Err(
            json!({"reminder": Option::<GetReminder>::None, "errors": vec!["Unknown error"]}),
        );
    }

    match sqlx::query_as_unchecked!(
        GetReminder,
        "
        SELECT
         reminders.attachment_name,
         reminders.avatar,
         channels.channel,
         reminders.content,
         reminders.embed_author,
         reminders.embed_author_url,
         reminders.embed_color,
         reminders.embed_description,
         reminders.embed_footer,
         reminders.embed_footer_url,
         reminders.embed_image_url,
         reminders.embed_thumbnail_url,
         reminders.embed_title,
         reminders.embed_fields,
         reminders.enabled,
         reminders.expires,
         reminders.interval_seconds,
         reminders.interval_days,
         reminders.interval_months,
         reminders.name,
         reminders.restartable,
         reminders.tts,
         reminders.uid,
         reminders.username,
         reminders.utc_time
        FROM reminders
        LEFT JOIN channels ON channels.id = reminders.channel_id
        WHERE uid = ?",
        reminder.uid
    )
    .fetch_one(pool.inner())
    .await
    {
        Ok(reminder) => Ok(json!({"reminder": reminder, "errors": error})),

        Err(e) => {
            warn!("Error exiting `edit_reminder': {:?}", e);

            Err(json!({"reminder": Option::<GetReminder>::None, "errors": vec!["Unknown error"]}))
        }
    }
}
