use crate::web::{
    check_authorization,
    guards::transaction::Transaction,
    routes::{
        dashboard::{create_reminder, CreateReminder, ImportBody, ReminderCsv},
        JsonResult,
    },
};
use crate::Database;
use base64::{prelude::BASE64_STANDARD, Engine};
use csv::{QuoteStyle, WriterBuilder};
use log::warn;
use rocket::{
    get,
    http::CookieJar,
    put,
    serde::json::{json, Json},
    State,
};
use serenity::{
    client::Context,
    model::id::{GuildId, UserId},
};
use sqlx::{MySql, Pool};

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

    let mut csv_writer = WriterBuilder::new().quote_style(QuoteStyle::Always).from_writer(vec![]);

    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,
                    reminders.attachment_name,
                    reminders.avatar,
                    CONCAT('#', channels.channel) AS 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,
                    CONVERT(CAST(embed_fields AS BINARY) USING utf8mb4) AS embed_fields,
                    reminders.enabled,
                    reminders.expires,
                    reminders.interval_seconds,
                    reminders.interval_days,
                    reminders.interval_months,
                    reminders.name,
                    reminders.restartable,
                    reminders.tts,
                    reminders.username,
                    reminders.utc_time
                FROM reminders
                LEFT JOIN channels
                    ON channels.id = reminders.channel_id
                WHERE status = 'pending'
                    AND channels.channel IN ({placeholder})
                "
            );

            let mut query = sqlx::query_as::<Database, ReminderCsv>(&sql);
            for channel in channels {
                query = query.bind(channel);
            }
            let result = query.fetch_all(pool.inner()).await;

            match result {
                Ok(reminders) => {
                    reminders.iter().for_each(|reminder| {
                        csv_writer.serialize(reminder).unwrap();
                    });

                    match csv_writer.into_inner() {
                        Ok(inner) => match String::from_utf8(inner) {
                            Ok(encoded) => Ok(json!({ "body": encoded })),

                            Err(e) => {
                                warn!("Failed to write UTF-8: {:?}", e);

                                Err(json!({"error": "Failed to write UTF-8"}))
                            }
                        },

                        Err(e) => {
                            warn!("Failed to extract CSV: {:?}", e);

                            Err(json!({"error": "Failed to extract CSV"}))
                        }
                    }
                }

                Err(e) => {
                    warn!("Failed to complete SQL query: {:?}", e);

                    Err(json!({"error": "Failed to query reminders"}))
                }
            }
        }

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

            Err(json!({"error": "Failed to get guild channels"}))
        }
    }
}

#[put("/api/guild/<id>/export/reminders", data = "<body>")]
pub async fn import(
    id: u64,
    cookies: &CookieJar<'_>,
    body: Json<ImportBody>,
    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 BASE64_STANDARD.decode(&body.body) {
        Ok(body) => {
            let mut reader = csv::Reader::from_reader(body.as_slice());
            let mut count = 0;

            for result in reader.deserialize::<ReminderCsv>() {
                match result {
                    Ok(record) => {
                        let channel_id = record.channel.split_at(1).1;

                        match channel_id.parse::<u64>() {
                            Ok(channel_id) => {
                                let reminder = CreateReminder {
                                    attachment: record.attachment,
                                    attachment_name: record.attachment_name,
                                    avatar: record.avatar,
                                    channel: channel_id,
                                    content: record.content,
                                    embed_author: record.embed_author,
                                    embed_author_url: record.embed_author_url,
                                    embed_color: record.embed_color,
                                    embed_description: record.embed_description,
                                    embed_footer: record.embed_footer,
                                    embed_footer_url: record.embed_footer_url,
                                    embed_image_url: record.embed_image_url,
                                    embed_thumbnail_url: record.embed_thumbnail_url,
                                    embed_title: record.embed_title,
                                    embed_fields: record
                                        .embed_fields
                                        .map(|s| serde_json::from_str(&s).ok())
                                        .flatten(),
                                    enabled: record.enabled,
                                    expires: record.expires,
                                    interval_seconds: record.interval_seconds,
                                    interval_days: record.interval_days,
                                    interval_months: record.interval_months,
                                    name: record.name,
                                    restartable: record.restartable,
                                    tts: record.tts,
                                    username: record.username,
                                    utc_time: record.utc_time,
                                };

                                create_reminder(
                                    ctx.inner(),
                                    &mut transaction,
                                    GuildId::new(id),
                                    UserId::new(user_id),
                                    reminder,
                                )
                                .await?;

                                count += 1;
                            }

                            Err(_) => {
                                return json_err!(format!(
                                    "Failed to parse channel {}",
                                    channel_id
                                ));
                            }
                        }
                    }

                    Err(e) => {
                        warn!("Couldn't deserialize CSV row: {:?}", e);

                        return json_err!("Deserialize error. Aborted");
                    }
                }
            }

            match transaction.commit().await {
                Ok(_) => Ok(json!({
                    "message": format!("Imported {} reminders", count)
                })),

                Err(e) => {
                    warn!("Failed to commit transaction: {:?}", e);
                    json_err!("Couldn't commit transaction")
                }
            }
        }

        Err(_) => {
            json_err!("Malformed base64")
        }
    }
}
