package dev.lions.user.manager.service.impl;

import dev.lions.user.manager.client.KeycloakAdminClient;
import dev.lions.user.manager.dto.user.UserDTO;
import dev.lions.user.manager.dto.user.UserSearchCriteriaDTO;
import dev.lions.user.manager.dto.user.UserSearchResultDTO;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * Tests d'intégration pour UserServiceImpl - Cas limites et branches conditionnelles complexes
 */
@ExtendWith(MockitoExtension.class)
class UserServiceImplIntegrationTest {

    @Mock
    private KeycloakAdminClient keycloakAdminClient;

    @Mock
    private UsersResource usersResource;

    @Mock
    private UserResource userResource;

    @InjectMocks
    private UserServiceImpl userService;

    private static final String REALM = "test-realm";
    private static final String USER_ID = "user-123";

    // ==================== Tests de recherche - Cas limites ====================

    @Test
    void testSearchUsers_WithSearchTerm() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation user = new UserRepresentation();
        user.setId("1");
        user.setUsername("testuser");
        user.setEnabled(true);
        user.setEmailVerified(true);

        when(usersResource.search("test", 0, 20)).thenReturn(List.of(user));
        when(usersResource.count()).thenReturn(1);

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .searchTerm("test")
            .page(0)
            .pageSize(20)
            .build();

        UserSearchResultDTO result = userService.searchUsers(criteria);

        assertNotNull(result);
        assertEquals(1, result.getUsers().size());
        verify(usersResource).search("test", 0, 20);
    }

    @Test
    void testSearchUsers_WithSearchTerm_Blank() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation user = new UserRepresentation();
        user.setId("1");
        user.setUsername("user1");
        user.setEnabled(true);

        when(usersResource.list(0, 20)).thenReturn(List.of(user));
        when(usersResource.count()).thenReturn(1);

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .searchTerm("   ") // Blank string
            .page(0)
            .pageSize(20)
            .build();

        UserSearchResultDTO result = userService.searchUsers(criteria);

        assertNotNull(result);
        verify(usersResource).list(0, 20); // Should use list() when searchTerm is blank
    }

    @Test
    void testSearchUsers_WithUsername() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation user = new UserRepresentation();
        user.setId("1");
        user.setUsername("exactuser");
        user.setEnabled(true);

        when(usersResource.search("exactuser", 0, 20, true)).thenReturn(List.of(user));
        when(usersResource.count()).thenReturn(1);

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .username("exactuser")
            .page(0)
            .pageSize(20)
            .build();

        UserSearchResultDTO result = userService.searchUsers(criteria);

        assertNotNull(result);
        verify(usersResource).search("exactuser", 0, 20, true);
    }

    @Test
    void testSearchUsers_WithEmail() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation user = new UserRepresentation();
        user.setId("1");
        user.setEmail("test@example.com");
        user.setEnabled(true);

        when(usersResource.searchByEmail("test@example.com", true)).thenReturn(List.of(user));
        when(usersResource.count()).thenReturn(1);

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .email("test@example.com")
            .page(0)
            .pageSize(20)
            .build();

        UserSearchResultDTO result = userService.searchUsers(criteria);

        assertNotNull(result);
        verify(usersResource).searchByEmail("test@example.com", true);
    }

    @Test
    void testSearchUsers_NoCriteria() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation user = new UserRepresentation();
        user.setId("1");
        user.setUsername("user1");
        user.setEnabled(true);

        when(usersResource.list(0, 20)).thenReturn(List.of(user));
        when(usersResource.count()).thenReturn(1);

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .page(0)
            .pageSize(20)
            .build();

        UserSearchResultDTO result = userService.searchUsers(criteria);

        assertNotNull(result);
        verify(usersResource).list(0, 20);
    }

    // ==================== Tests de filtrage - Cas limites ====================

    @Test
    void testSearchUsers_FilterByEnabled_True() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation enabledUser = new UserRepresentation();
        enabledUser.setId("1");
        enabledUser.setUsername("enabled");
        enabledUser.setEnabled(true);

        UserRepresentation disabledUser = new UserRepresentation();
        disabledUser.setId("2");
        disabledUser.setUsername("disabled");
        disabledUser.setEnabled(false);

        when(usersResource.list(0, 20)).thenReturn(List.of(enabledUser, disabledUser));
        when(usersResource.count()).thenReturn(2);

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .enabled(true)
            .page(0)
            .pageSize(20)
            .build();

        UserSearchResultDTO result = userService.searchUsers(criteria);

        assertNotNull(result);
        assertEquals(1, result.getUsers().size());
        assertTrue(result.getUsers().get(0).getEnabled());
    }

    @Test
    void testSearchUsers_FilterByEnabled_False() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation enabledUser = new UserRepresentation();
        enabledUser.setId("1");
        enabledUser.setUsername("enabled");
        enabledUser.setEnabled(true);

        UserRepresentation disabledUser = new UserRepresentation();
        disabledUser.setId("2");
        disabledUser.setUsername("disabled");
        disabledUser.setEnabled(false);

        when(usersResource.list(0, 20)).thenReturn(List.of(enabledUser, disabledUser));
        when(usersResource.count()).thenReturn(2);

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .enabled(false)
            .page(0)
            .pageSize(20)
            .build();

        UserSearchResultDTO result = userService.searchUsers(criteria);

        assertNotNull(result);
        assertEquals(1, result.getUsers().size());
        assertFalse(result.getUsers().get(0).getEnabled());
    }

    @Test
    void testSearchUsers_FilterByEmailVerified_True() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation verifiedUser = new UserRepresentation();
        verifiedUser.setId("1");
        verifiedUser.setUsername("verified");
        verifiedUser.setEmail("test@example.com");
        verifiedUser.setEmailVerified(true);
        verifiedUser.setEnabled(true);

        UserRepresentation unverifiedUser = new UserRepresentation();
        unverifiedUser.setId("2");
        unverifiedUser.setUsername("unverified");
        unverifiedUser.setEmail("test2@example.com");
        unverifiedUser.setEmailVerified(false);
        unverifiedUser.setEnabled(true);

        when(usersResource.list(0, 20)).thenReturn(List.of(verifiedUser, unverifiedUser));
        when(usersResource.count()).thenReturn(2);

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .emailVerified(true)
            .page(0)
            .pageSize(20)
            .build();

        UserSearchResultDTO result = userService.searchUsers(criteria);

        assertNotNull(result);
        assertEquals(1, result.getUsers().size());
        assertTrue(result.getUsers().get(0).getEmailVerified());
    }

    @Test
    void testSearchUsers_FilterByEnabledAndEmailVerified() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation user1 = new UserRepresentation();
        user1.setId("1");
        user1.setUsername("user1");
        user1.setEnabled(true);
        user1.setEmailVerified(true);

        UserRepresentation user2 = new UserRepresentation();
        user2.setId("2");
        user2.setUsername("user2");
        user2.setEnabled(true);
        user2.setEmailVerified(false);

        UserRepresentation user3 = new UserRepresentation();
        user3.setId("3");
        user3.setUsername("user3");
        user3.setEnabled(false);
        user3.setEmailVerified(true);

        when(usersResource.list(0, 20)).thenReturn(List.of(user1, user2, user3));
        when(usersResource.count()).thenReturn(3);

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .enabled(true)
            .emailVerified(true)
            .page(0)
            .pageSize(20)
            .build();

        UserSearchResultDTO result = userService.searchUsers(criteria);

        assertNotNull(result);
        assertEquals(1, result.getUsers().size());
        assertEquals("user1", result.getUsers().get(0).getUsername());
    }

    @Test
    void testSearchUsers_FilterByEnabled_Null() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation user1 = new UserRepresentation();
        user1.setId("1");
        user1.setUsername("user1");
        user1.setEnabled(true);

        UserRepresentation user2 = new UserRepresentation();
        user2.setId("2");
        user2.setUsername("user2");
        user2.setEnabled(false);

        when(usersResource.list(0, 20)).thenReturn(List.of(user1, user2));
        when(usersResource.count()).thenReturn(2);

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .enabled(null) // Null should not filter
            .page(0)
            .pageSize(20)
            .build();

        UserSearchResultDTO result = userService.searchUsers(criteria);

        assertNotNull(result);
        assertEquals(2, result.getUsers().size()); // Both users should be returned
    }

    // ==================== Tests getUserById - Cas limites ====================

    @Test
    void testGetUserById_WithRealmRoles() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);

        UserRepresentation userRep = new UserRepresentation();
        userRep.setId(USER_ID);
        userRep.setUsername("testuser");
        userRep.setEnabled(true);
        when(userResource.toRepresentation()).thenReturn(userRep);

        RoleRepresentation role1 = new RoleRepresentation();
        role1.setName("role1");
        RoleRepresentation role2 = new RoleRepresentation();
        role2.setName("role2");

        when(userResource.roles()).thenReturn(mock(org.keycloak.admin.client.resource.RoleMappingResource.class));
        when(userResource.roles().realmLevel()).thenReturn(mock(org.keycloak.admin.client.resource.RoleScopeResource.class));
        when(userResource.roles().realmLevel().listAll()).thenReturn(List.of(role1, role2));

        Optional<UserDTO> result = userService.getUserById(USER_ID, REALM);

        assertTrue(result.isPresent());
        assertEquals(USER_ID, result.get().getId());
    }

    @Test
    void testGetUserById_WithEmptyRealmRoles() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);

        UserRepresentation userRep = new UserRepresentation();
        userRep.setId(USER_ID);
        userRep.setUsername("testuser");
        userRep.setEnabled(true);
        when(userResource.toRepresentation()).thenReturn(userRep);

        when(userResource.roles()).thenReturn(mock(org.keycloak.admin.client.resource.RoleMappingResource.class));
        when(userResource.roles().realmLevel()).thenReturn(mock(org.keycloak.admin.client.resource.RoleScopeResource.class));
        when(userResource.roles().realmLevel().listAll()).thenReturn(Collections.emptyList());

        Optional<UserDTO> result = userService.getUserById(USER_ID, REALM);

        assertTrue(result.isPresent());
        assertTrue(result.get().getRealmRoles() == null || result.get().getRealmRoles().isEmpty());
    }

    @Test
    void testGetUserById_WithNullRealmRoles() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);

        UserRepresentation userRep = new UserRepresentation();
        userRep.setId(USER_ID);
        userRep.setUsername("testuser");
        userRep.setEnabled(true);
        when(userResource.toRepresentation()).thenReturn(userRep);

        when(userResource.roles()).thenReturn(mock(org.keycloak.admin.client.resource.RoleMappingResource.class));
        when(userResource.roles().realmLevel()).thenReturn(mock(org.keycloak.admin.client.resource.RoleScopeResource.class));
        when(userResource.roles().realmLevel().listAll()).thenReturn(null);

        Optional<UserDTO> result = userService.getUserById(USER_ID, REALM);

        assertTrue(result.isPresent());
    }

    @Test
    void testGetUserById_WithExceptionInRolesRetrieval() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);

        UserRepresentation userRep = new UserRepresentation();
        userRep.setId(USER_ID);
        userRep.setUsername("testuser");
        userRep.setEnabled(true);
        when(userResource.toRepresentation()).thenReturn(userRep);

        when(userResource.roles()).thenReturn(mock(org.keycloak.admin.client.resource.RoleMappingResource.class));
        when(userResource.roles().realmLevel()).thenReturn(mock(org.keycloak.admin.client.resource.RoleScopeResource.class));
        when(userResource.roles().realmLevel().listAll()).thenThrow(new RuntimeException("Error getting roles"));

        // Should not throw exception, just log warning
        Optional<UserDTO> result = userService.getUserById(USER_ID, REALM);

        assertTrue(result.isPresent());
    }

    @Test
    void testGetUserById_With404InExceptionMessage() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        when(userResource.toRepresentation()).thenThrow(new RuntimeException("Server response is: 404"));

        Optional<UserDTO> result = userService.getUserById(USER_ID, REALM);

        assertFalse(result.isPresent());
    }

    @Test
    void testGetUserById_With404InExceptionMessage_Variant() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        when(userResource.toRepresentation()).thenThrow(new RuntimeException("Received: 'Server response is: 404'"));

        Optional<UserDTO> result = userService.getUserById(USER_ID, REALM);

        assertFalse(result.isPresent());
    }

    // ==================== Tests usernameExists et emailExists ====================

    @Test
    void testUsernameExists_True() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation user = new UserRepresentation();
        user.setUsername("existinguser");
        when(usersResource.search("existinguser", 0, 1, true)).thenReturn(List.of(user));

        boolean exists = userService.usernameExists("existinguser", REALM);

        assertTrue(exists);
    }

    @Test
    void testUsernameExists_False() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.search("nonexistent", 0, 1, true)).thenReturn(Collections.emptyList());

        boolean exists = userService.usernameExists("nonexistent", REALM);

        assertFalse(exists);
    }

    @Test
    void testUsernameExists_Exception() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.search("erroruser", 0, 1, true)).thenThrow(new RuntimeException("Error"));

        boolean exists = userService.usernameExists("erroruser", REALM);

        assertFalse(exists); // Should return false on exception
    }

    @Test
    void testEmailExists_True() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);

        UserRepresentation user = new UserRepresentation();
        user.setEmail("existing@example.com");
        when(usersResource.searchByEmail("existing@example.com", true)).thenReturn(List.of(user));

        boolean exists = userService.emailExists("existing@example.com", REALM);

        assertTrue(exists);
    }

    @Test
    void testEmailExists_False() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.searchByEmail("nonexistent@example.com", true)).thenReturn(Collections.emptyList());

        boolean exists = userService.emailExists("nonexistent@example.com", REALM);

        assertFalse(exists);
    }

    @Test
    void testEmailExists_Exception() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.searchByEmail("error@example.com", true)).thenThrow(new RuntimeException("Error"));

        boolean exists = userService.emailExists("error@example.com", REALM);

        assertFalse(exists); // Should return false on exception
    }

    // ==================== Tests countUsers ====================

    @Test
    void testCountUsers_Success() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.count()).thenReturn(42);

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .build();

        long count = userService.countUsers(criteria);

        assertEquals(42L, count);
    }

    @Test
    void testCountUsers_Exception() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.count()).thenThrow(new RuntimeException("Error"));

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .build();

        long count = userService.countUsers(criteria);

        assertEquals(0L, count); // Should return 0 on exception
    }

    // ==================== Tests searchUsers - Exception handling ====================

    @Test
    void testSearchUsers_Exception() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.list(0, 20)).thenThrow(new RuntimeException("Connection error"));

        UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
            .realmName(REALM)
            .page(0)
            .pageSize(20)
            .build();

        assertThrows(RuntimeException.class, () -> userService.searchUsers(criteria));
    }
}

