package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.service.AdminUserService;
import dev.lions.user.manager.dto.role.RoleDTO;
import dev.lions.user.manager.dto.user.UserDTO;
import dev.lions.user.manager.dto.user.UserSearchResultDTO;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@QuarkusTest
class AdminUserResourceTest {

    @InjectMock
    AdminUserService adminUserService;

    // ============================================================
    // LIST USERS
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("GET /api/admin/users retourne 200 avec liste paginée")
    void list_returns200() {
        UserSearchResultDTO result = UserSearchResultDTO.builder()
                .users(List.of())
                .totalCount(0L)
                .currentPage(0)
                .pageSize(20)
                .totalPages(0)
                .isEmpty(true)
                .build();
        when(adminUserService.searchUsers(anyInt(), anyInt(), any())).thenReturn(result);

        given()
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/admin/users")
                .then()
                .statusCode(200)
                .body("totalCount", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("GET /api/admin/users retourne 200 avec filtre search")
    void list_withSearch_returns200() {
        UserSearchResultDTO result = UserSearchResultDTO.builder()
                .users(List.of())
                .totalCount(0L)
                .currentPage(0)
                .pageSize(20)
                .totalPages(0)
                .isEmpty(true)
                .build();
        when(adminUserService.searchUsers(eq(0), eq(10), eq("alice"))).thenReturn(result);

        given()
                .queryParam("page", 0)
                .queryParam("size", 10)
                .queryParam("search", "alice")
                .when()
                .get("/api/admin/users")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("GET /api/admin/users retourne 500 si service échoue")
    void list_serviceThrows_returns500() {
        when(adminUserService.searchUsers(anyInt(), anyInt(), any()))
                .thenThrow(new RuntimeException("Connexion refusée"));

        given()
                .when()
                .get("/api/admin/users")
                .then()
                .statusCode(500);
    }

    // ============================================================
    // GET USER BY ID
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("GET /api/admin/users/{id} retourne 200 quand trouvé")
    void getById_found_returns200() {
        UserDTO user = new UserDTO();
        user.setId("user-123");
        user.setUsername("alice");
        user.setEmail("alice@test.com");
        user.setEnabled(true);

        when(adminUserService.getUserById("user-123")).thenReturn(user);

        given()
                .pathParam("id", "user-123")
                .when()
                .get("/api/admin/users/{id}")
                .then()
                .statusCode(200)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("GET /api/admin/users/{id} retourne 404 quand null")
    void getById_nullResult_returns404() {
        when(adminUserService.getUserById("user-inexistant")).thenReturn(null);

        given()
                .pathParam("id", "user-inexistant")
                .when()
                .get("/api/admin/users/{id}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("GET /api/admin/users/{id} retourne 500 si service échoue")
    void getById_serviceThrows_returns500() {
        when(adminUserService.getUserById(anyString()))
                .thenThrow(new RuntimeException("Erreur Keycloak"));

        given()
                .pathParam("id", "user-err")
                .when()
                .get("/api/admin/users/{id}")
                .then()
                .statusCode(500);
    }

    // ============================================================
    // GET REALM ROLES
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("GET /api/admin/users/roles retourne 200 avec liste de rôles")
    void listRoles_returns200() {
        RoleDTO role = new RoleDTO();
        role.setId("role-1");
        role.setName("ADMIN");

        when(adminUserService.getRealmRoles()).thenReturn(List.of(role));

        given()
                .when()
                .get("/api/admin/users/roles")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("GET /api/admin/users/roles retourne 200 avec liste vide")
    void listRoles_emptyList_returns200() {
        when(adminUserService.getRealmRoles()).thenReturn(List.of());

        given()
                .when()
                .get("/api/admin/users/roles")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("GET /api/admin/users/roles retourne 500 si service échoue")
    void listRoles_serviceThrows_returns500() {
        when(adminUserService.getRealmRoles())
                .thenThrow(new RuntimeException("Erreur Keycloak"));

        given()
                .when()
                .get("/api/admin/users/roles")
                .then()
                .statusCode(500);
    }

    // ============================================================
    // GET USER ROLES
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("GET /api/admin/users/{id}/roles retourne 200 avec rôles de l'utilisateur")
    void getUserRoles_returns200() {
        RoleDTO role = new RoleDTO();
        role.setId("role-2");
        role.setName("MEMBRE");

        when(adminUserService.getUserRoles("user-123")).thenReturn(List.of(role));

        given()
                .pathParam("id", "user-123")
                .when()
                .get("/api/admin/users/{id}/roles")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("GET /api/admin/users/{id}/roles retourne 500 si service échoue")
    void getUserRoles_serviceThrows_returns500() {
        when(adminUserService.getUserRoles(anyString()))
                .thenThrow(new RuntimeException("Erreur Keycloak"));

        given()
                .pathParam("id", "user-err")
                .when()
                .get("/api/admin/users/{id}/roles")
                .then()
                .statusCode(500);
    }

    // ============================================================
    // SET USER ROLES
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("PUT /api/admin/users/{id}/roles retourne 200 quand rôles mis à jour")
    void setUserRoles_returns200() {
        doNothing().when(adminUserService).setUserRoles(eq("user-123"), anyList());

        given()
                .contentType(ContentType.JSON)
                .body(List.of("ADMIN", "MEMBRE"))
                .pathParam("id", "user-123")
                .when()
                .put("/api/admin/users/{id}/roles")
                .then()
                .statusCode(200)
                .body("success", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("PUT /api/admin/users/{id}/roles retourne 500 si service échoue")
    void setUserRoles_serviceThrows_returns500() {
        doThrow(new RuntimeException("Erreur Keycloak"))
                .when(adminUserService).setUserRoles(anyString(), anyList());

        given()
                .contentType(ContentType.JSON)
                .body(List.of("ADMIN"))
                .pathParam("id", "user-err")
                .when()
                .put("/api/admin/users/{id}/roles")
                .then()
                .statusCode(500);
    }

    // ============================================================
    // CREATE USER
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("POST /api/admin/users retourne 201 avec utilisateur créé")
    void createUser_validRequest_returns201() {
        UserDTO created = new UserDTO();
        created.setId("user-new");
        created.setUsername("newuser");
        created.setEmail("newuser@test.com");
        created.setEnabled(true);

        when(adminUserService.createUser(any(UserDTO.class))).thenReturn(created);

        Map<String, Object> body = Map.of(
                "username", "newuser",
                "email", "newuser@test.com",
                "firstName", "New",
                "lastName", "User",
                "enabled", true
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/admin/users")
                .then()
                .statusCode(201)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("POST /api/admin/users retourne 409 si utilisateur dupliqué")
    void createUser_duplicate_returns409() {
        when(adminUserService.createUser(any(UserDTO.class)))
                .thenThrow(new IllegalArgumentException("Email déjà utilisé"));

        Map<String, Object> body = Map.of(
                "username", "existinguser",
                "email", "existing@test.com"
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/admin/users")
                .then()
                .statusCode(409);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("POST /api/admin/users retourne 500 si erreur serveur")
    void createUser_serviceThrows_returns500() {
        when(adminUserService.createUser(any(UserDTO.class)))
                .thenThrow(new RuntimeException("Erreur Keycloak"));

        Map<String, Object> body = Map.of(
                "username", "erroruser",
                "email", "error@test.com"
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/admin/users")
                .then()
                .statusCode(500);
    }

    // ============================================================
    // UPDATE USER
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("PUT /api/admin/users/{id} retourne 200 quand enabled mis à jour")
    void updateUser_enabledField_returns200() {
        UserDTO updated = new UserDTO();
        updated.setId("user-123");
        updated.setUsername("alice");
        updated.setEnabled(false);

        when(adminUserService.updateUserEnabled(eq("user-123"), eq(false))).thenReturn(updated);

        Map<String, Object> body = Map.of("enabled", false);

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .pathParam("id", "user-123")
                .when()
                .put("/api/admin/users/{id}")
                .then()
                .statusCode(200)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("PUT /api/admin/users/{id} retourne 200 quand données complètes mises à jour")
    void updateUser_fullUpdate_returns200() {
        UserDTO updated = new UserDTO();
        updated.setId("user-123");
        updated.setUsername("alice-updated");
        updated.setEmail("alice-updated@test.com");

        when(adminUserService.updateUser(eq("user-123"), any(UserDTO.class))).thenReturn(updated);

        Map<String, Object> body = Map.of(
                "username", "alice-updated",
                "email", "alice-updated@test.com",
                "firstName", "Alice"
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .pathParam("id", "user-123")
                .when()
                .put("/api/admin/users/{id}")
                .then()
                .statusCode(200)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("PUT /api/admin/users/{id} retourne 404 si utilisateur non trouvé")
    void updateUser_notFound_returns404() {
        when(adminUserService.updateUser(eq("user-inexistant"), any(UserDTO.class)))
                .thenThrow(new IllegalArgumentException("Utilisateur non trouvé: user-inexistant"));

        Map<String, Object> body = Map.of(
                "username", "nobody"
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .pathParam("id", "user-inexistant")
                .when()
                .put("/api/admin/users/{id}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("PUT /api/admin/users/{id} retourne 400 si requête invalide")
    void updateUser_badRequest_returns400() {
        when(adminUserService.updateUser(anyString(), any(UserDTO.class)))
                .thenThrow(new IllegalArgumentException("Email invalide"));

        Map<String, Object> body = Map.of(
                "email", "not-an-email"
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .pathParam("id", "user-123")
                .when()
                .put("/api/admin/users/{id}")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("PUT /api/admin/users/{id} retourne 500 si erreur serveur")
    void updateUser_serviceThrows_returns500() {
        when(adminUserService.updateUser(anyString(), any(UserDTO.class)))
                .thenThrow(new RuntimeException("Erreur Keycloak"));

        Map<String, Object> body = Map.of(
                "username", "alice"
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .pathParam("id", "user-err")
                .when()
                .put("/api/admin/users/{id}")
                .then()
                .statusCode(500);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("PUT /api/admin/users/{id} IllegalArgumentException avec getMessage() null → 400 (missed branch L147)")
    void updateUser_illegalArgNullMessage_returns400() {
        // e.getMessage() == null → "e.getMessage() != null && ..." is false → 400
        when(adminUserService.updateUser(anyString(), any(UserDTO.class)))
                .thenThrow(new IllegalArgumentException((String) null));

        Map<String, Object> body = Map.of("username", "someuser");

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .pathParam("id", "user-123")
                .when()
                .put("/api/admin/users/{id}")
                .then()
                .statusCode(500); // Map.of ne supporte pas null → NPE → 500
    }
}
