use chrono::{naive::NaiveDateTime, Utc};
use futures::TryFutureExt;
use log::warn;
use rocket::serde::json::json;
use serde::{Deserialize, Serialize};
use serenity::{client::Context, model::id::UserId};
use sqlx::types::Json;

use crate::utils::check_subscription;
use crate::web::{
    consts::{
        DAY, MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH, MAX_EMBED_DESCRIPTION_LENGTH,
        MAX_EMBED_FIELDS, MAX_EMBED_FIELD_TITLE_LENGTH, MAX_EMBED_FIELD_VALUE_LENGTH,
        MAX_EMBED_FOOTER_LENGTH, MAX_EMBED_TITLE_LENGTH, MAX_NAME_LENGTH, MAX_URL_LENGTH,
        MIN_INTERVAL,
    },
    guards::transaction::Transaction,
    routes::{
        dashboard::{create_database_channel, generate_uid, name_default, Attachment, EmbedField},
        JsonResult,
    },
    Error,
};

#[derive(Serialize, Deserialize)]
pub struct Reminder {
    pub attachment: Option<Attachment>,
    pub attachment_name: Option<String>,
    pub content: String,
    pub embed_author: String,
    pub embed_author_url: Option<String>,
    pub embed_color: u32,
    pub embed_description: String,
    pub embed_footer: String,
    pub embed_footer_url: Option<String>,
    pub embed_image_url: Option<String>,
    pub embed_thumbnail_url: Option<String>,
    pub embed_title: String,
    pub embed_fields: Option<Json<Vec<EmbedField>>>,
    pub enabled: bool,
    pub expires: Option<NaiveDateTime>,
    pub interval_seconds: Option<u32>,
    pub interval_days: Option<u32>,
    pub interval_months: Option<u32>,
    #[serde(default = "name_default")]
    pub name: String,
    pub tts: bool,
    #[serde(default)]
    pub uid: String,
    pub utc_time: NaiveDateTime,
}

pub async fn create_reminder(
    ctx: &Context,
    transaction: &mut Transaction<'_>,
    user_id: UserId,
    reminder: Reminder,
) -> JsonResult {
    let channel = user_id
        .create_dm_channel(&ctx)
        .map_err(|e| Error::Serenity(e))
        .and_then(|dm_channel| create_database_channel(&ctx, dm_channel.id, transaction))
        .await;

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

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

        return Err(json!({"error": error_msg}));
    }

    let channel = channel.unwrap();

    // validate lengths
    check_length!(MAX_NAME_LENGTH, reminder.name);
    check_length!(MAX_CONTENT_LENGTH, reminder.content);
    check_length!(MAX_EMBED_DESCRIPTION_LENGTH, reminder.embed_description);
    check_length!(MAX_EMBED_TITLE_LENGTH, reminder.embed_title);
    check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder.embed_author);
    check_length!(MAX_EMBED_FOOTER_LENGTH, reminder.embed_footer);
    check_length_opt!(MAX_EMBED_FIELDS, reminder.embed_fields);
    if let Some(fields) = &reminder.embed_fields {
        for field in &fields.0 {
            check_length!(MAX_EMBED_FIELD_VALUE_LENGTH, field.value);
            check_length!(MAX_EMBED_FIELD_TITLE_LENGTH, field.title);
        }
    }
    check_length_opt!(
        MAX_URL_LENGTH,
        reminder.embed_footer_url,
        reminder.embed_thumbnail_url,
        reminder.embed_author_url,
        reminder.embed_image_url
    );

    // validate urls
    check_url_opt!(
        reminder.embed_footer_url,
        reminder.embed_thumbnail_url,
        reminder.embed_author_url,
        reminder.embed_image_url
    );

    // validate time and interval
    if reminder.utc_time < Utc::now().naive_utc() {
        return Err(json!({"error": "Time must be in the future"}));
    }
    if reminder.interval_seconds.is_some()
        || reminder.interval_days.is_some()
        || reminder.interval_months.is_some()
    {
        if reminder.interval_months.unwrap_or(0) * 30 * DAY as u32
            + reminder.interval_days.unwrap_or(0) * DAY as u32
            + reminder.interval_seconds.unwrap_or(0)
            < *MIN_INTERVAL
        {
            return Err(json!({"error": "Interval too short"}));
        }
    }

    // check patreon if necessary
    if reminder.interval_seconds.is_some()
        || reminder.interval_days.is_some()
        || reminder.interval_months.is_some()
    {
        if !check_subscription(&ctx, transaction.executor(), user_id, None).await {
            return Err(json!({"error": "Patreon is required to set intervals"}));
        }
    }

    let name = if reminder.name.is_empty() { name_default() } else { reminder.name.clone() };
    let new_uid = generate_uid();

    // write to db
    match sqlx::query!(
        "INSERT INTO reminders (
         uid,
         attachment,
         attachment_name,
         channel_id,
         content,
         embed_author,
         embed_author_url,
         embed_color,
         embed_description,
         embed_footer,
         embed_footer_url,
         embed_image_url,
         embed_thumbnail_url,
         embed_title,
         embed_fields,
         enabled,
         expires,
         interval_seconds,
         interval_days,
         interval_months,
         name,
         tts,
         `utc_time`
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
        new_uid,
        reminder.attachment,
        reminder.attachment_name,
        channel,
        reminder.content,
        reminder.embed_author,
        reminder.embed_author_url,
        reminder.embed_color,
        reminder.embed_description,
        reminder.embed_footer,
        reminder.embed_footer_url,
        reminder.embed_image_url,
        reminder.embed_thumbnail_url,
        reminder.embed_title,
        reminder.embed_fields,
        reminder.enabled,
        reminder.expires,
        reminder.interval_seconds,
        reminder.interval_days,
        reminder.interval_months,
        name,
        reminder.tts,
        reminder.utc_time,
    )
    .execute(transaction.executor())
    .await
    {
        Ok(_) => sqlx::query_as_unchecked!(
            Reminder,
            "SELECT
             reminders.attachment,
             reminders.attachment_name,
             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.tts,
             reminders.uid,
             reminders.utc_time
            FROM reminders
            WHERE uid = ?",
            new_uid
        )
        .fetch_one(transaction.executor())
        .await
        .map(|r| Ok(json!(r)))
        .unwrap_or_else(|e| {
            warn!("Failed to complete SQL query: {:?}", e);

            Err(json!({"error": "Could not load reminder"}))
        }),

        Err(e) => {
            warn!("Error in `create_reminder`: Could not execute query: {:?}", e);

            Err(json!({"error": "Unknown error"}))
        }
    }
}
