use crate::{
    entities::logged_user::LoggedUser,
    services::folder::app::{
        controller::{create_app, delete_app, read_app, update_app},
        persistence::check_app_id,
    },
    util::serde_ext::parse_from_payload,
};
use actix_web::{
    web::{Data, Payload},
    HttpResponse,
};
use anyhow::Context;
use backend_service::{
    errors::{invalid_params, ServerError},
    response::FlowyResponse,
};
use flowy_folder_data_model::{
    parser::app::{AppDesc, AppName},
    protobuf::{AppId as AppIdPB, CreateAppParams as CreateAppParamsPB, UpdateAppParams as UpdateAppParamsPB},
};
use protobuf::Message;
use sqlx::PgPool;

pub async fn create_handler(
    payload: Payload,
    pool: Data<PgPool>,
    logged_user: LoggedUser,
) -> Result<HttpResponse, ServerError> {
    let params: CreateAppParamsPB = parse_from_payload(payload).await?;
    let mut transaction = pool
        .begin()
        .await
        .context("Failed to acquire a Postgres connection to create app")?;

    let app = create_app(&mut transaction, params, logged_user).await?;

    transaction
        .commit()
        .await
        .context("Failed to commit SQL transaction to create app.")?;

    Ok(FlowyResponse::success().pb(app)?.into())
}

pub async fn read_handler(payload: Payload, pool: Data<PgPool>, user: LoggedUser) -> Result<HttpResponse, ServerError> {
    let params: AppIdPB = parse_from_payload(payload).await?;
    let app_id = check_app_id(params.app_id)?;

    let mut transaction = pool
        .begin()
        .await
        .context("Failed to acquire a Postgres connection to read app")?;
    let app = read_app(&mut transaction, app_id, &user).await?;
    transaction
        .commit()
        .await
        .context("Failed to commit SQL transaction to read app.")?;

    Ok(FlowyResponse::success().pb(app)?.into())
}

pub async fn update_handler(payload: Payload, pool: Data<PgPool>) -> Result<HttpResponse, ServerError> {
    let params: UpdateAppParamsPB = parse_from_payload(payload).await?;
    let app_id = check_app_id(params.get_app_id().to_string())?;
    let name = match params.has_name() {
        false => None,
        true => Some(AppName::parse(params.get_name().to_owned()).map_err(invalid_params)?.0),
    };

    let color_style = match params.has_color_style() {
        false => None,
        true => {
            let color_bytes = params.get_color_style().write_to_bytes()?;
            Some(color_bytes)
        }
    };

    let desc = match params.has_desc() {
        false => None,
        true => Some(AppDesc::parse(params.get_desc().to_owned()).map_err(invalid_params)?.0),
    };

    let mut transaction = pool
        .begin()
        .await
        .context("Failed to acquire a Postgres connection to update app")?;

    let _ = update_app(&mut transaction, app_id, name, desc, color_style).await?;

    transaction
        .commit()
        .await
        .context("Failed to commit SQL transaction to update app.")?;
    Ok(FlowyResponse::success().into())
}

pub async fn delete_handler(payload: Payload, pool: Data<PgPool>) -> Result<HttpResponse, ServerError> {
    let params: AppIdPB = parse_from_payload(payload).await?;
    let app_id = check_app_id(params.app_id.to_owned())?;
    let mut transaction = pool
        .begin()
        .await
        .context("Failed to acquire a Postgres connection to delete app")?;

    let _ = delete_app(&mut transaction, app_id).await?;

    transaction
        .commit()
        .await
        .context("Failed to commit SQL transaction to delete app.")?;

    Ok(FlowyResponse::success().into())
}
