use anyhow::Context;
use axum::{
    extract::Query,
    response::{IntoResponse, Json},
    routing::get,
    Router,
};
use diesel::{dsl::insert_into, prelude::*, update};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::{
    api_keys::ApiKey,
    app_error::AppError,
    app_state::{AppState, DbConn},
    messages::Message,
    projects::Project,
    schema::{api_keys, messages, projects},
};

pub fn new_router(state: AppState) -> Router<AppState> {
    Router::new().route("/say", get(say_get)).with_state(state)
}

#[derive(Deserialize)]
struct SayQuery {
    key: Uuid,
    project: String,
    message: String,
}

async fn say_get(
    DbConn(db_conn): DbConn,
    Query(query): Query<SayQuery>,
) -> Result<impl IntoResponse, AppError> {
    let key = query.key.clone();
    let maybe_api_key = db_conn
        .interact(move |conn| {
            update(api_keys::table.filter(ApiKey::with_id(key)))
                .set(api_keys::last_used_at.eq(diesel::dsl::now))
                .returning(ApiKey::as_returning())
                .get_result(conn)
                .optional()
        })
        .await
        .unwrap()
        .context("unable to get API key")?;
    let api_key = match maybe_api_key {
        Some(api_key) => api_key,
        None => return Err(AppError::ForbiddenError("key not accepted".to_string())),
    };
    let project_name = query.project.to_lowercase();
    let project = db_conn
        .interact(move |conn| {
            insert_into(projects::table)
                .values((
                    projects::id.eq(Uuid::now_v7()),
                    projects::team_id.eq(api_key.team_id.clone()),
                    projects::name.eq(project_name.clone()),
                ))
                .on_conflict((projects::team_id, projects::name))
                .do_nothing()
                .execute(conn)?;
            // It would be nice to merge these two database operations into one,
            // but it's not trivial to do so without faking an update; refer to:
            // https://stackoverflow.com/a/42217872
            Project::all()
                .filter(Project::with_team(api_key.team_id))
                .filter(Project::with_name(project_name))
                .first(conn)
        })
        .await
        .unwrap()
        .context("unable to get project")?;
    db_conn
        .interact(move |conn| {
            insert_into(messages::table)
                .values(Message::values_now(project.id, query.message))
                .execute(conn)
        })
        .await
        .unwrap()
        .context("unable to insert message")?;
    #[derive(Serialize)]
    struct ResponseBody {
        ok: bool,
    }
    Ok(Json(ResponseBody { ok: true }))
}
