use defguard_common::db::models::group::Group;
use defguard_core::handlers::{Auth, EditGroupInfo, GroupInfo};
use reqwest::StatusCode;
use serde_json::json;
use sqlx::postgres::{PgConnectOptions, PgPoolOptions};

use super::common::{make_test_client, setup_pool};

#[sqlx::test]
async fn test_create_group(_: PgPoolOptions, options: PgConnectOptions) {
    let pool = setup_pool(options).await;

    let (client, client_state) = make_test_client(pool).await;

    // Authorize as an administrator.
    let auth = Auth::new("admin", "pass123");
    let response = client.post("/api/v1/auth").json(&auth).send().await;
    assert_eq!(response.status(), StatusCode::OK);

    // Create new group.
    let data = EditGroupInfo::new("hogwards", vec!["hpotter".into()], false);
    let response = client.post("/api/v1/group").json(&data).send().await;
    assert_eq!(response.status(), StatusCode::CREATED);

    // Try to create the same group again.
    let response = client.post("/api/v1/group").json(&data).send().await;
    assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);

    // Delete the group.
    let id = Group::find_by_name(&client_state.pool, "hogwards")
        .await
        .unwrap()
        .unwrap()
        .id;
    let response = client.delete(format!("/api/v1/group/{id}")).send().await;
    assert_eq!(response.status(), StatusCode::OK);

    // Try to delete again.
    let response = client.delete(format!("/api/v1/group/{id}")).send().await;
    assert_eq!(response.status(), StatusCode::NOT_FOUND);
}

#[sqlx::test]
async fn test_modify_group(_: PgPoolOptions, options: PgConnectOptions) {
    let pool = setup_pool(options).await;

    let (client, client_state) = make_test_client(pool).await;

    // Authorize as an administrator.
    let auth = Auth::new("admin", "pass123");
    let response = client.post("/api/v1/auth").json(&auth).send().await;
    assert_eq!(response.status(), StatusCode::OK);

    // Create new group.
    let data = EditGroupInfo::new("hogwards", vec!["hpotter".into()], false);
    let response = client.post("/api/v1/group").json(&data).send().await;
    assert_eq!(response.status(), StatusCode::CREATED);

    let id = Group::find_by_name(&client_state.pool, "hogwards")
        .await
        .unwrap()
        .unwrap()
        .id;

    // Rename group.
    let data = EditGroupInfo::new("gryffindor", Vec::new(), false);
    let response = client
        .put(format!("/api/v1/group/{id}"))
        .json(&data)
        .send()
        .await;
    assert_eq!(response.status(), StatusCode::OK);

    // Get group info – name should be updated.
    let response = client.get(format!("/api/v1/group/{id}")).send().await;
    assert_eq!(response.status(), StatusCode::OK);
    let group_info: GroupInfo = response.json().await;
    assert_eq!(group_info.name, "gryffindor");
}

#[sqlx::test]
async fn test_modify_group_members(_: PgPoolOptions, options: PgConnectOptions) {
    let pool = setup_pool(options).await;

    let (client, client_state) = make_test_client(pool).await;

    // Authorize as an administrator.
    let auth = Auth::new("admin", "pass123");
    let response = client.post("/api/v1/auth").json(&auth).send().await;
    assert_eq!(response.status(), StatusCode::OK);

    // Create new group.
    let data = EditGroupInfo::new("hogwards", vec!["hpotter".into()], false);
    let response = client.post("/api/v1/group").json(&data).send().await;
    assert_eq!(response.status(), StatusCode::CREATED);

    let id = Group::find_by_name(&client_state.pool, "hogwards")
        .await
        .unwrap()
        .unwrap()
        .id;

    // Get group info.
    let response = client.get(format!("/api/v1/group/{id}")).send().await;
    assert_eq!(response.status(), StatusCode::OK);
    let group_info: GroupInfo = response.json().await;
    assert_eq!(group_info.members, vec!["hpotter".to_string()]);

    // Change group members.
    let data = EditGroupInfo::new("hogwards", Vec::new(), false);
    let response = client
        .put(format!("/api/v1/group/{id}"))
        .json(&data)
        .send()
        .await;
    assert_eq!(response.status(), StatusCode::OK);

    // Get group info.
    let response = client.get(format!("/api/v1/group/{id}")).send().await;
    assert_eq!(response.status(), StatusCode::OK);
    let group_info: GroupInfo = response.json().await;
    assert!(group_info.members.is_empty());
}

#[sqlx::test]
async fn test_modify_group_no_locations_in_request(_: PgPoolOptions, options: PgConnectOptions) {
    let pool = setup_pool(options).await;

    let (client, client_state) = make_test_client(pool).await;

    // Authorize as an administrator.
    let auth = Auth::new("admin", "pass123");
    let response = client.post("/api/v1/auth").json(&auth).send().await;
    assert_eq!(response.status(), StatusCode::OK);

    // Create new group.
    let data = json!({
        "name": "hogwards",
        "members": [
            "hpotter",
            "admin"
        ],
        "is_admin": false
    });
    let response = client.post("/api/v1/group").json(&data).send().await;
    assert_eq!(response.status(), StatusCode::CREATED);

    let id = Group::find_by_name(&client_state.pool, "hogwards")
        .await
        .unwrap()
        .unwrap()
        .id;

    // Rename group.
    let data = json!({
        "name": "gryffindor",
        "members": [
            "hpotter",
        ],
        "is_admin": false
    });
    let response = client
        .put(format!("/api/v1/group/{id}"))
        .json(&data)
        .send()
        .await;
    assert_eq!(response.status(), StatusCode::OK);

    let response = client.get(format!("/api/v1/group/{id}")).send().await;
    assert_eq!(response.status(), StatusCode::OK);
    let group_info: GroupInfo = response.json().await;
    assert_eq!(group_info.name, "gryffindor");
    assert_eq!(group_info.members, vec!["hpotter"]);
}

#[sqlx::test]
async fn test_remove_last_admin_group(_: PgPoolOptions, options: PgConnectOptions) {
    let pool = setup_pool(options).await;

    let (client, client_state) = make_test_client(pool).await;

    // Authorize as an administrator.
    let auth = Auth::new("admin", "pass123");
    let response = client.post("/api/v1/auth").json(&auth).send().await;
    assert_eq!(response.status(), StatusCode::OK);

    let id = Group::find_by_name(&client_state.pool, "admin")
        .await
        .unwrap()
        .unwrap()
        .id;

    // Get group info.
    let response = client.get(format!("/api/v1/group/{id}")).send().await;
    assert_eq!(response.status(), StatusCode::OK);
    let group_info: GroupInfo = response.json().await;
    assert_eq!(group_info.members, vec!["admin".to_string()]);

    let response = client.delete(format!("/api/v1/group/{id}")).send().await;
    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}

#[sqlx::test]
async fn test_modify_last_admin_group(_: PgPoolOptions, options: PgConnectOptions) {
    let pool = setup_pool(options).await;

    let (client, client_state) = make_test_client(pool).await;

    // Authorize as an administrator.
    let auth = Auth::new("admin", "pass123");
    let response = client.post("/api/v1/auth").json(&auth).send().await;
    assert_eq!(response.status(), StatusCode::OK);

    let id = Group::find_by_name(&client_state.pool, "admin")
        .await
        .unwrap()
        .unwrap()
        .id;

    // Get group info.
    let response = client.get(format!("/api/v1/group/{id}")).send().await;
    assert_eq!(response.status(), StatusCode::OK);
    let group_info: GroupInfo = response.json().await;
    assert_eq!(group_info.members, vec!["admin".to_string()]);

    // try to remove admin status from the last group
    let data = json!({
        "name": "admin",
        "members": [
            "admin",
        ],
        "is_admin": false
    });
    let response = client
        .put(format!("/api/v1/group/{id}"))
        .json(&data)
        .send()
        .await;
    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}

#[sqlx::test]
async fn test_group_endpoints_access(_: PgPoolOptions, options: PgConnectOptions) {
    let pool = setup_pool(options).await;

    // Auth client as normal user without admin access
    let (client, _) = make_test_client(pool).await;

    let auth = Auth::new("hpotter", "pass123");
    let response = client.post("/api/v1/auth").json(&auth).send().await;
    assert_eq!(response.status(), StatusCode::OK);

    let response = client.get("/api/v1/group").send().await;
    assert_eq!(response.status(), StatusCode::FORBIDDEN);

    let response = client.get("/api/v1/group/1").send().await;
    assert_eq!(response.status(), StatusCode::FORBIDDEN);

    let response = client.delete("/api/v1/group/1").send().await;
    assert_eq!(response.status(), StatusCode::FORBIDDEN);

    let data = EditGroupInfo::new("hogwards", vec!["hpotter".into(), "admin".into()], true);
    let response = client.post("/api/v1/group").json(&data).send().await;
    assert_eq!(response.status(), StatusCode::FORBIDDEN);

    let data = EditGroupInfo::new("admin", vec!["hpotter".into()], true);
    let response = client.put("/api/v1/group/1").json(&data).send().await;
    assert_eq!(response.status(), StatusCode::FORBIDDEN);
}
