package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.client.RoleServiceClient;
import dev.lions.unionflow.server.client.UserServiceClient;
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 jakarta.inject.Inject;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.util.List;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;

@QuarkusTest
class AdminUserServiceTest {

    @Inject
    AdminUserService adminUserService;

    @InjectMock
    @RestClient
    UserServiceClient userServiceClient;

    @InjectMock
    @RestClient
    RoleServiceClient roleServiceClient;

    @Test
    @DisplayName("searchUsers appelle le client rest UserService")
    void searchUsers_callsClient() {
        UserSearchResultDTO mockResult = new UserSearchResultDTO();
        Mockito.when(userServiceClient.searchUsers(any())).thenReturn(mockResult);

        UserSearchResultDTO result = adminUserService.searchUsers(0, 10, "test");

        assertThat(result).isNotNull();
        Mockito.verify(userServiceClient).searchUsers(any());
    }

    @Test
    @DisplayName("getUserById retourne l'utilisateur via le client")
    void getUserById_returnsUser() {
        String userId = UUID.randomUUID().toString();
        UserDTO mockUser = new UserDTO();
        mockUser.setId(userId);
        mockUser.setUsername("testuser");

        Mockito.when(userServiceClient.getUserById(eq(userId), any())).thenReturn(mockUser);

        UserDTO result = adminUserService.getUserById(userId);

        assertThat(result).isNotNull();
        assertThat(result.getUsername()).isEqualTo("testuser");
    }

    @Test
    @DisplayName("createUser appelle le client pour la création")
    void createUser_callsClient() {
        UserDTO user = new UserDTO();
        user.setUsername("newuser");

        Mockito.when(userServiceClient.createUser(any(), any())).thenReturn(user);

        UserDTO result = adminUserService.createUser(user);

        assertThat(result).isNotNull();
        assertThat(result.getUsername()).isEqualTo("newuser");
    }

    // =========================================================================
    // getRealmRoles
    // =========================================================================

    @Test
    @DisplayName("getRealmRoles retourne la liste des rôles")
    void getRealmRoles_returnsRoles() {
        RoleDTO role = new RoleDTO();
        role.setName("admin");

        Mockito.when(roleServiceClient.getRealmRoles("unionflow")).thenReturn(List.of(role));

        List<RoleDTO> result = adminUserService.getRealmRoles();

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getName()).isEqualTo("admin");
    }

    @Test
    @DisplayName("getRealmRoles retourne liste vide si le client lève une exception")
    void getRealmRoles_exceptionRetourneListeVide() {
        Mockito.when(roleServiceClient.getRealmRoles(any()))
                .thenThrow(new RuntimeException("Service unavailable"));

        List<RoleDTO> result = adminUserService.getRealmRoles();

        assertThat(result).isEmpty();
    }

    // =========================================================================
    // getUserRoles
    // =========================================================================

    @Test
    @DisplayName("getUserRoles retourne les rôles de l'utilisateur")
    void getUserRoles_returnsUserRoles() {
        String userId = UUID.randomUUID().toString();
        RoleDTO role = new RoleDTO();
        role.setName("membre");

        Mockito.when(roleServiceClient.getUserRealmRoles(eq(userId), any())).thenReturn(List.of(role));

        List<RoleDTO> result = adminUserService.getUserRoles(userId);

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getName()).isEqualTo("membre");
    }

    @Test
    @DisplayName("getUserRoles retourne liste vide si le client lève une exception")
    void getUserRoles_exceptionRetourneListeVide() {
        String userId = UUID.randomUUID().toString();
        Mockito.when(roleServiceClient.getUserRealmRoles(eq(userId), any()))
                .thenThrow(new RuntimeException("Keycloak down"));

        List<RoleDTO> result = adminUserService.getUserRoles(userId);

        assertThat(result).isEmpty();
    }

    // =========================================================================
    // updateUser
    // =========================================================================

    @Test
    @DisplayName("updateUser appelle le client REST avec userId et realm")
    void updateUser_callsClient() {
        String userId = UUID.randomUUID().toString();
        UserDTO user = new UserDTO();
        user.setUsername("updated-user");

        Mockito.when(userServiceClient.updateUser(eq(userId), any(), any())).thenReturn(user);

        UserDTO result = adminUserService.updateUser(userId, user);

        assertThat(result).isNotNull();
        assertThat(result.getUsername()).isEqualTo("updated-user");
        Mockito.verify(userServiceClient).updateUser(eq(userId), any(), eq("unionflow"));
    }

    // =========================================================================
    // updateUserEnabled
    // =========================================================================

    @Test
    @DisplayName("updateUserEnabled active l'utilisateur et appelle updateUser")
    void updateUserEnabled_activerUtilisateur() {
        String userId = UUID.randomUUID().toString();
        UserDTO existing = new UserDTO();
        existing.setId(userId);
        existing.setUsername("testuser");
        existing.setEnabled(false);

        UserDTO updated = new UserDTO();
        updated.setId(userId);
        updated.setEnabled(true);

        Mockito.when(userServiceClient.getUserById(eq(userId), any())).thenReturn(existing);
        Mockito.when(userServiceClient.updateUser(eq(userId), any(), any())).thenReturn(updated);

        UserDTO result = adminUserService.updateUserEnabled(userId, true);

        assertThat(result).isNotNull();
        Mockito.verify(userServiceClient).updateUser(eq(userId), any(), eq("unionflow"));
    }

    @Test
    @DisplayName("updateUserEnabled lance IllegalArgumentException si l'utilisateur est null")
    void updateUserEnabled_utilisateurNonTrouve_lancerException() {
        String userId = UUID.randomUUID().toString();
        Mockito.when(userServiceClient.getUserById(eq(userId), any())).thenReturn(null);

        assertThatThrownBy(() -> adminUserService.updateUserEnabled(userId, true))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("Utilisateur non trouvé");
    }

    // =========================================================================
    // setUserRoles
    // =========================================================================

    @Test
    @DisplayName("setUserRoles assigne les nouveaux rôles et révoque les retirés")
    void setUserRoles_assigneEtRevoque() {
        String userId = UUID.randomUUID().toString();

        RoleDTO existingRole = new RoleDTO();
        existingRole.setName("membre");

        // L'utilisateur a actuellement "membre", on veut lui assigner "admin"
        Mockito.when(roleServiceClient.getUserRealmRoles(eq(userId), any()))
                .thenReturn(List.of(existingRole));

        // assign "admin", revoke "membre"
        adminUserService.setUserRoles(userId, List.of("admin"));

        Mockito.verify(roleServiceClient).assignRealmRoles(eq(userId), eq("unionflow"), any());
        Mockito.verify(roleServiceClient).revokeRealmRoles(eq(userId), eq("unionflow"), any());
    }

    @Test
    @DisplayName("setUserRoles sans changement — ni assign ni revoke appelé")
    void setUserRoles_sansChangement_aucunAppel() {
        String userId = UUID.randomUUID().toString();

        RoleDTO existingRole = new RoleDTO();
        existingRole.setName("admin");

        // L'utilisateur a déjà "admin", on veut "admin" → rien à faire
        Mockito.when(roleServiceClient.getUserRealmRoles(eq(userId), any()))
                .thenReturn(List.of(existingRole));

        adminUserService.setUserRoles(userId, List.of("admin"));

        Mockito.verify(roleServiceClient, Mockito.never()).assignRealmRoles(any(), any(), any());
        Mockito.verify(roleServiceClient, Mockito.never()).revokeRealmRoles(any(), any(), any());
    }

    @Test
    @DisplayName("setUserRoles avec liste cible vide — tous les rôles existants révoqués")
    void setUserRoles_listeCibleNull_tousRevoque() {
        String userId = UUID.randomUUID().toString();

        RoleDTO existingRole = new RoleDTO();
        existingRole.setName("membre");

        Mockito.when(roleServiceClient.getUserRealmRoles(eq(userId), any()))
                .thenReturn(List.of(existingRole));

        adminUserService.setUserRoles(userId, new java.util.ArrayList<>());

        // rien à assigner, "membre" à révoquer
        Mockito.verify(roleServiceClient, Mockito.never()).assignRealmRoles(any(), any(), any());
        Mockito.verify(roleServiceClient).revokeRealmRoles(eq(userId), eq("unionflow"), any());
    }

    @Test
    @DisplayName("setUserRoles null targetRoleNames — couvre branche null L107")
    void setUserRoles_targetRolesNull_revoqueTous() {
        String userId = UUID.randomUUID().toString();

        RoleDTO existingRole = new RoleDTO();
        existingRole.setName("admin");

        Mockito.when(roleServiceClient.getUserRealmRoles(eq(userId), any()))
                .thenReturn(List.of(existingRole));

        // null → toAssign = List.of(), toRevoke = currentNames − List.of() = currentNames
        adminUserService.setUserRoles(userId, null);

        Mockito.verify(roleServiceClient, Mockito.never()).assignRealmRoles(any(), any(), any());
        Mockito.verify(roleServiceClient).revokeRealmRoles(eq(userId), eq("unionflow"), any());
    }
}
