use crate::{dtos::MyceliumProfileData, rest::shared::PaginationParams};

use actix_web::{delete, get, patch, post, web, Responder};
use myc_core::{
    domain::dtos::{
        http::HttpMethod,
        http_secret::HttpSecret,
        webhook::{deserialize_write_method, WebHook, WebHookTrigger},
    },
    models::AccountLifeCycle,
    use_cases::role_scoped::system_manager::webhook::{
        delete_webhook, list_webhooks, register_webhook, update_webhook,
    },
};
use myc_diesel::repositories::SqlAppModule;
use myc_http_tools::{
    utils::HttpJsonResponse,
    wrappers::default_response_to_http_response::{
        create_response_kind, delete_response_kind, fetch_many_response_kind,
        handle_mapped_error, updating_response_kind,
    },
};
use serde::Deserialize;
use shaku::HasComponent;
use utoipa::{IntoParams, ToSchema};
use uuid::Uuid;

// ? ---------------------------------------------------------------------------
// ? Configure application
// ? ---------------------------------------------------------------------------

pub fn configure(config: &mut web::ServiceConfig) {
    config
        .service(crate_webhook_url)
        .service(list_webhooks_url)
        .service(update_webhook_url)
        .service(delete_webhook_url);
}

// ? ---------------------------------------------------------------------------
// ? Define API structs
// ? ---------------------------------------------------------------------------

#[derive(Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateWebHookBody {
    name: String,
    description: Option<String>,
    url: String,
    trigger: WebHookTrigger,
    #[serde(deserialize_with = "deserialize_write_method")]
    method: Option<HttpMethod>,
    secret: Option<HttpSecret>,
}

#[derive(Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateWebHookBody {
    name: Option<String>,
    description: Option<String>,
    secret: Option<HttpSecret>,
    is_active: Option<bool>,
}

#[derive(Deserialize, ToSchema, IntoParams)]
#[serde(rename_all = "camelCase")]
pub struct ListWebHooksParams {
    name: Option<String>,
    trigger: Option<WebHookTrigger>,
}

// ? ---------------------------------------------------------------------------
// ? Define endpoints
// ? ---------------------------------------------------------------------------

/// Create a webhook
#[utoipa::path(
    post,
    operation_id = "create_webhook",
    request_body = CreateWebHookBody,
    responses(
        (
            status = 500,
            description = "Unknown internal server error.",
            body = HttpJsonResponse,
        ),
        (
            status = 403,
            description = "Forbidden.",
            body = HttpJsonResponse,
        ),
        (
            status = 401,
            description = "Unauthorized.",
            body = HttpJsonResponse,
        ),
        (
            status = 201,
            description = "WebHook created.",
            body = WebHook,
        ),
        (
            status = 200,
            description = "WebHook already exists.",
            body = WebHook,
        ),
    ),
)]
#[post("")]
pub async fn crate_webhook_url(
    body: web::Json<CreateWebHookBody>,
    profile: MyceliumProfileData,
    life_cycle_settings: web::Data<AccountLifeCycle>,
    app_module: web::Data<SqlAppModule>,
) -> impl Responder {
    match register_webhook(
        profile.to_profile(),
        body.name.to_owned(),
        body.description.to_owned(),
        body.url.to_owned(),
        body.trigger.to_owned(),
        body.method.to_owned(),
        body.secret.to_owned(),
        life_cycle_settings.get_ref().to_owned(),
        Box::new(&*app_module.resolve_ref()),
    )
    .await
    {
        Ok(res) => create_response_kind(res),
        Err(err) => handle_mapped_error(err),
    }
}

/// List webhooks
#[utoipa::path(
    get,
    operation_id = "list_webhooks",
    params(
        ListWebHooksParams,
    ),
    responses(
        (
            status = 500,
            description = "Unknown internal server error.",
            body = HttpJsonResponse,
        ),
        (
            status = 204,
            description = "Not found.",
        ),
        (
            status = 403,
            description = "Forbidden.",
            body = HttpJsonResponse,
        ),
        (
            status = 401,
            description = "Unauthorized.",
            body = HttpJsonResponse,
        ),
        (
            status = 200,
            description = "Fetching success.",
            body = WebHook,
        ),
    ),
)]
#[get("")]
pub async fn list_webhooks_url(
    info: web::Query<ListWebHooksParams>,
    page: web::Query<PaginationParams>,
    profile: MyceliumProfileData,
    app_module: web::Data<SqlAppModule>,
) -> impl Responder {
    match list_webhooks(
        profile.to_profile(),
        info.name.to_owned(),
        info.trigger.to_owned(),
        page.page_size.to_owned(),
        page.skip.to_owned(),
        Box::new(&*app_module.resolve_ref()),
    )
    .await
    {
        Ok(res) => fetch_many_response_kind(res),
        Err(err) => handle_mapped_error(err),
    }
}

/// Update a webhook
#[utoipa::path(
    patch,
    operation_id = "update_webhook",
    params(
        ("webhook_id" = Uuid, Path, description = "The webhook primary key."),
    ),
    request_body = UpdateWebHookBody,
    responses(
        (
            status = 500,
            description = "Unknown internal server error.",
            body = HttpJsonResponse,
        ),
        (
            status = 403,
            description = "Forbidden.",
            body = HttpJsonResponse,
        ),
        (
            status = 401,
            description = "Unauthorized.",
            body = HttpJsonResponse,
        ),
        (
            status = 202,
            description = "WebHook created.",
            body = WebHook,
        ),
    ),
)]
#[patch("/{webhook_id}")]
pub async fn update_webhook_url(
    body: web::Json<UpdateWebHookBody>,
    path: web::Path<Uuid>,
    profile: MyceliumProfileData,
    life_cycle_settings: web::Data<AccountLifeCycle>,
    app_module: web::Data<SqlAppModule>,
) -> impl Responder {
    match update_webhook(
        profile.to_profile(),
        path.to_owned(),
        body.name.to_owned(),
        body.description.to_owned(),
        body.secret.to_owned(),
        life_cycle_settings.get_ref().to_owned(),
        body.is_active.to_owned(),
        Box::new(&*app_module.resolve_ref()),
        Box::new(&*app_module.resolve_ref()),
    )
    .await
    {
        Ok(res) => updating_response_kind(res),
        Err(err) => handle_mapped_error(err),
    }
}

/// Delete a webhook
#[utoipa::path(
    delete,
    operation_id = "delete_webhook",
    params(
        ("webhook_id" = Uuid, Path, description = "The webhook primary key."),
    ),
    responses(
        (
            status = 500,
            description = "Unknown internal server error.",
            body = HttpJsonResponse,
        ),
        (
            status = 400,
            description = "Webhook not deleted.",
            body = HttpJsonResponse,
        ),
        (
            status = 401,
            description = "Unauthorized.",
            body = HttpJsonResponse,
        ),
        (
            status = 403,
            description = "Forbidden.",
            body = HttpJsonResponse,
        ),
        (
            status = 204,
            description = "Webhook deleted.",
        ),
    ),
)]
#[delete("/{webhook_id}")]
pub async fn delete_webhook_url(
    path: web::Path<Uuid>,
    profile: MyceliumProfileData,
    app_module: web::Data<SqlAppModule>,
) -> impl Responder {
    match delete_webhook(
        profile.to_profile(),
        path.to_owned(),
        Box::new(&*app_module.resolve_ref()),
    )
    .await
    {
        Ok(res) => delete_response_kind(res),
        Err(err) => handle_mapped_error(err),
    }
}
