use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use brass_macros::db_test;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;

use crate::{
    endpoints::IdPath,
    models::{Function, Role, User},
    utils::ApplicationError,
};

#[cfg(test)]
use crate::utils::test_helper::{test_post, DbTestContext, RequestConfig, StatusCode};
#[cfg(test)]
use fake::{
    faker::{internet::raw::SafeEmail, name::raw::Name},
    locales::EN,
    Fake,
};

#[derive(Deserialize, Serialize)]
pub struct EditUserForm {
    email: String,
    name: String,
    role: u8,
    function: u8,
    area: Option<i32>,
}

#[actix_web::post("/users/edit/{id}")]
pub async fn post_edit(
    user: web::ReqData<User>,
    pool: web::Data<PgPool>,
    path: web::Path<IdPath>,
    form: web::Form<EditUserForm>,
) -> Result<impl Responder, ApplicationError> {
    if user.role != Role::AreaManager && user.role != Role::Admin {
        return Err(ApplicationError::Unauthorized);
    }

    if user.id == path.id {
        return Ok(HttpResponse::BadRequest().finish());
    }

    let Some(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await? else {
        return Ok(HttpResponse::NotFound().finish());
    };

    if user.role == Role::AreaManager && user.area_id != user_in_db.area_id {
        return Err(ApplicationError::Unauthorized);
    }

    let mut changed = false;

    let email = if user_in_db.email != form.email {
        changed = true;
        Some(form.email.as_str())
    } else {
        None
    };

    let name = if user_in_db.name != form.name {
        changed = true;
        Some(form.name.as_str())
    } else {
        None
    };

    let role = if user_in_db.role as u8 != form.role {
        if let Ok(r) = Role::try_from(form.role) {
            changed = true;
            Some(r)
        } else {
            None
        }
    } else {
        None
    };

    let function = if user_in_db.function as u8 != form.function {
        if let Ok(f) = Function::try_from(form.function) {
            changed = true;
            Some(f)
        } else {
            None
        }
    } else {
        None
    };

    let area = if user.role == Role::Admin
        && form.area.is_some()
        && user_in_db.area_id != form.area.unwrap()
    {
        changed = true;
        Some(form.area.unwrap())
    } else {
        None
    };

    if changed {
        User::update(
            pool.get_ref(),
            path.id,
            email,
            name,
            None,
            None,
            role,
            function,
            area,
            None,
            None,
        )
        .await?;
    }

    Ok(HttpResponse::Found()
        .insert_header((LOCATION, "/users"))
        .insert_header(("HX-LOCATION", "/users"))
        .finish())
}

#[db_test]
async fn works_when_user_is_admin(context: &DbTestContext) {
    User::create(
        &context.db_pool,
        &Name(EN).fake::<String>(),
        &SafeEmail(EN).fake::<String>(),
        Role::Staff,
        Function::Posten,
        1,
    )
    .await
    .unwrap();

    crate::models::Area::create(&context.db_pool, "Süd")
        .await
        .unwrap();

    let app = context.app().await;
    let config = RequestConfig {
        uri: "/users/edit/1".to_string(),
        role: Role::Admin,
        function: crate::models::Function::Posten,
        user_area: 1,
    };

    let new_name: String = Name(EN).fake();
    let new_mail: String = SafeEmail(EN).fake();

    let form = EditUserForm {
        name: new_name.clone(),
        email: new_mail.clone(),
        role: Role::AreaManager as u8,
        function: Function::Fuehrungsassistent as u8,
        area: Some(2),
    };

    let response = test_post(&context.db_pool, app, &config, form).await;
    assert_eq!(StatusCode::FOUND, response.status());

    let updated_user = User::read_by_id(&context.db_pool, 1)
        .await
        .unwrap()
        .unwrap();

    assert_eq!(new_name, updated_user.name);
    assert_eq!(new_mail, updated_user.email);
    assert_eq!(Role::AreaManager, updated_user.role);
    assert_eq!(Function::Fuehrungsassistent, updated_user.function);
    assert_eq!(2, updated_user.area_id);
}

#[db_test]
async fn cant_edit_oneself(context: &DbTestContext) {
    let app = context.app().await;
    let config = RequestConfig {
        uri: "/users/edit/1".to_string(),
        role: Role::Admin,
        function: crate::models::Function::Posten,
        user_area: 1,
    };

    let form = EditUserForm {
        name: "".to_string(),
        email: "".to_string(),
        role: Role::AreaManager as u8,
        function: Function::Fuehrungsassistent as u8,
        area: Some(1),
    };

    let response = test_post(&context.db_pool, app, &config, form).await;
    assert_eq!(StatusCode::BAD_REQUEST, response.status());
}
