use log::warn;
use rocket::{
    delete, get,
    http::CookieJar,
    patch, post,
    serde::json::{json, Json},
    State,
};
use serde::{Deserialize, Serialize};
use serenity::{
    all::{ChannelId, GuildId},
    prelude::Context,
};

use crate::web::{
    check_authorization,
    guards::transaction::Transaction,
    routes::{dashboard::check_channel_matches_guild, JsonResult},
    string_opt,
};

#[derive(Deserialize)]
pub struct CreateTodo {
    #[serde(with = "string_opt")]
    channel_id: Option<u64>,
    value: String,
}

#[derive(Serialize)]
pub struct GetTodo {
    id: u32,
    #[serde(with = "string_opt")]
    channel_id: Option<u64>,
    value: String,
}

#[derive(Deserialize)]
pub struct UpdateTodo {
    value: String,
}

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

    let guild_id = GuildId::new(id);
    if todo.value.len() > 2000 {
        return json_err!("Value too long");
    }

    match todo.channel_id {
        Some(channel_id) => {
            if !check_channel_matches_guild(ctx, ChannelId::new(channel_id), guild_id) {
                warn!("Channel {} not found for guild {}", channel_id, guild_id);

                return json_err!("Channel not found");
            }

            sqlx::query!(
                "
                INSERT INTO todos (guild_id, channel_id, value)
                VALUES (
                    (SELECT id FROM guilds WHERE guild = ?),
                    (SELECT id FROM channels WHERE channel = ?),
                    ?
                )
                ",
                id,
                channel_id,
                todo.value
            )
            .execute(transaction.executor())
            .await
            .map_err(|e| {
                warn!("Error creating todo: {:?}", e);
                json!({"errors": vec!["Unknown error"]})
            })?;
        }

        None => {
            sqlx::query!(
                "
                INSERT INTO todos (guild_id, channel_id, value)
                VALUES (
                    (SELECT id FROM guilds WHERE guild = ?),
                    NULL,
                    ?
                )
                ",
                id,
                todo.value
            )
            .execute(transaction.executor())
            .await
            .map_err(|e| {
                warn!("Error creating todo: {:?}", e);
                json!({"errors": vec!["Unknown error"]})
            })?;
        }
    }

    if let Err(e) = transaction.commit().await {
        warn!("Couldn't commit transaction: {:?}", e);
        return json_err!("Couldn't commit transaction.");
    }

    Ok(json!({}))
}

#[get("/api/guild/<id>/todos")]
pub async fn get_todo(
    id: u64,
    cookies: &CookieJar<'_>,
    ctx: &State<Context>,
    mut transaction: Transaction<'_>,
) -> JsonResult {
    check_authorization(cookies, ctx.inner(), id).await?;

    let todos = sqlx::query_as!(
        GetTodo,
        "
        SELECT
            todos.id,
            channels.channel AS channel_id,
            value
        FROM todos
        INNER JOIN guilds
        ON guilds.id = todos.guild_id
        LEFT JOIN channels
        ON channels.id = todos.channel_id
        WHERE guilds.guild = ?
        ",
        id
    )
    .fetch_all(transaction.executor())
    .await
    .map_err(|e| {
        warn!("Error fetching todos: {:?}", e);
        json!({ "errors": vec!["Unknown error"] })
    })?;

    Ok(json!(todos))
}

#[patch("/api/guild/<guild_id>/todos/<todo_id>", data = "<todo>")]
pub async fn update_todo(
    guild_id: u64,
    todo_id: u64,
    todo: Json<UpdateTodo>,
    cookies: &CookieJar<'_>,
    ctx: &State<Context>,
    mut transaction: Transaction<'_>,
) -> JsonResult {
    check_authorization(cookies, ctx.inner(), guild_id).await?;

    if todo.value.len() > 2000 {
        return json_err!("Value too long");
    }

    sqlx::query!(
        "
        UPDATE todos
        SET value = ?
        WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)
            AND id = ?
        ",
        todo.value,
        guild_id,
        todo_id,
    )
    .execute(transaction.executor())
    .await
    .map_err(|e| {
        warn!("Error updating todo: {:?}", e);
        json!({"errors": vec!["Unknown error"]})
    })?;

    if let Err(e) = transaction.commit().await {
        warn!("Couldn't commit transaction: {:?}", e);
        return json_err!("Couldn't commit transaction.");
    }

    Ok(json!({}))
}

#[delete("/api/guild/<guild_id>/todos/<todo_id>")]
pub async fn delete_todo(
    guild_id: u64,
    todo_id: u64,
    cookies: &CookieJar<'_>,
    ctx: &State<Context>,
    mut transaction: Transaction<'_>,
) -> JsonResult {
    check_authorization(cookies, ctx.inner(), guild_id).await?;

    sqlx::query!(
        "
        DELETE FROM todos
        WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)
            AND id = ?
        ",
        guild_id,
        todo_id,
    )
    .execute(transaction.executor())
    .await
    .map_err(|e| {
        warn!("Error deleting todo: {:?}", e);
        json!({"errors": vec!["Unknown error"]})
    })?;

    if let Err(e) = transaction.commit().await {
        warn!("Couldn't commit transaction: {:?}", e);
        return json_err!("Couldn't commit transaction.");
    }

    Ok(json!({}))
}
