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

import dev.lions.user.manager.client.KeycloakAdminClient;
import dev.lions.user.manager.dto.user.UserDTO;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.keycloak.admin.client.resource.RoleMappingResource;
import org.keycloak.admin.client.resource.RoleScopeResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
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 supplémentaires pour UserServiceImpl pour améliorer la couverture
 */
@ExtendWith(MockitoExtension.class)
class UserServiceImplExtendedTest {

    @Mock
    private KeycloakAdminClient keycloakAdminClient;

    @Mock
    private UsersResource usersResource;

    @Mock
    private UserResource userResource;

    @Mock
    private RoleMappingResource roleMappingResource;

    @Mock
    private RoleScopeResource roleScopeResource;

    @InjectMocks
    private UserServiceImpl userService;

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

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

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

        userService.deactivateUser(USER_ID, REALM, "Test reason");

        verify(userResource).update(argThat(rep -> !rep.isEnabled()));
    }

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

        userService.resetPassword(USER_ID, REALM, "newPassword123", true);

        verify(userResource).resetPassword(any());
    }

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

        userService.sendVerificationEmail(USER_ID, REALM);

        verify(userResource).sendVerifyEmail();
    }

    @Test
    void testLogoutAllSessions() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        when(userResource.getUserSessions()).thenReturn(Collections.emptyList());

        int count = userService.logoutAllSessions(USER_ID, REALM);

        verify(userResource).logout();
        assertEquals(0, count);
    }

    @Test
    void testGetActiveSessions() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        
        // Mock UserSessionRepresentation
        org.keycloak.representations.idm.UserSessionRepresentation session1 = 
            mock(org.keycloak.representations.idm.UserSessionRepresentation.class);
        when(session1.getId()).thenReturn("session-1");
        org.keycloak.representations.idm.UserSessionRepresentation session2 = 
            mock(org.keycloak.representations.idm.UserSessionRepresentation.class);
        when(session2.getId()).thenReturn("session-2");
        
        when(userResource.getUserSessions()).thenReturn(List.of(session1, session2));

        List<String> sessions = userService.getActiveSessions(USER_ID, REALM);

        assertNotNull(sessions);
        assertEquals(2, sessions.size());
        assertTrue(sessions.contains("session-1"));
        assertTrue(sessions.contains("session-2"));
    }

    @Test
    void testGetActiveSessions_Empty() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        when(userResource.getUserSessions()).thenReturn(Collections.emptyList());

        List<String> sessions = userService.getActiveSessions(USER_ID, REALM);

        assertNotNull(sessions);
        assertTrue(sessions.isEmpty());
    }

    @Test
    void testGetActiveSessions_Exception() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        when(userResource.getUserSessions()).thenThrow(new RuntimeException("Error"));

        List<String> sessions = userService.getActiveSessions(USER_ID, REALM);

        assertNotNull(sessions);
        assertTrue(sessions.isEmpty());
    }

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

        UserRepresentation user1 = new UserRepresentation();
        user1.setId("user-1");
        user1.setUsername("user1");
        user1.setEnabled(true); // Important: définir enabled pour éviter NullPointerException
        UserRepresentation user2 = new UserRepresentation();
        user2.setId("user-2");
        user2.setUsername("user2");
        user2.setEnabled(true); // Important: définir enabled pour éviter NullPointerException

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

        var result = userService.getAllUsers(REALM, 0, 20);

        assertNotNull(result);
        assertEquals(2, result.getUsers().size());
        assertEquals(2L, result.getTotalCount());
    }

    @Test
    void testGetUserById_NotFound() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        when(userResource.toRepresentation()).thenThrow(new jakarta.ws.rs.NotFoundException());

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

        assertFalse(result.isPresent());
    }

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

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

        assertFalse(result.isPresent());
    }

    @Test
    void testGetUserByUsername_Success() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        
        UserRepresentation userRep = new UserRepresentation();
        userRep.setId(USER_ID);
        userRep.setUsername("testuser");
        userRep.setEnabled(true);
        when(usersResource.search("testuser", 0, 1, true)).thenReturn(List.of(userRep));

        Optional<UserDTO> result = userService.getUserByUsername("testuser", REALM);

        assertTrue(result.isPresent());
        assertEquals("testuser", result.get().getUsername());
    }

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

        Optional<UserDTO> result = userService.getUserByUsername("nonexistent", REALM);

        assertFalse(result.isPresent());
    }

    @Test
    void testGetUserByUsername_Exception() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.search("testuser", 0, 1, true)).thenThrow(new RuntimeException("Connection error"));

        assertThrows(RuntimeException.class, () -> 
            userService.getUserByUsername("testuser", REALM));
    }

    @Test
    void testGetUserByEmail_Success() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        
        UserRepresentation userRep = new UserRepresentation();
        userRep.setId(USER_ID);
        userRep.setEmail("test@example.com");
        userRep.setEnabled(true);
        when(usersResource.searchByEmail("test@example.com", true)).thenReturn(List.of(userRep));

        Optional<UserDTO> result = userService.getUserByEmail("test@example.com", REALM);

        assertTrue(result.isPresent());
        assertEquals("test@example.com", result.get().getEmail());
    }

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

        Optional<UserDTO> result = userService.getUserByEmail("nonexistent@example.com", REALM);

        assertFalse(result.isPresent());
    }

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

        assertThrows(RuntimeException.class, () -> 
            userService.getUserByEmail("test@example.com", REALM));
    }

    @Test
    void testCreateUser_UsernameExists() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        // usernameExists calls search which should return a non-empty list
        UserRepresentation existingUser = new UserRepresentation();
        existingUser.setUsername("existinguser");
        existingUser.setEnabled(true);
        when(usersResource.search("existinguser", 0, 1, true)).thenReturn(List.of(existingUser));

        UserDTO userDTO = UserDTO.builder()
            .username("existinguser")
            .email("test@example.com")
            .build();

        // createUser catches all exceptions and rethrows as RuntimeException
        RuntimeException exception = assertThrows(RuntimeException.class, () -> 
            userService.createUser(userDTO, REALM));
        assertTrue(exception.getCause() instanceof IllegalArgumentException);
        assertTrue(exception.getCause().getMessage().contains("existe déjà"));
    }

    @Test
    void testCreateUser_EmailExists() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.search("newuser", 0, 1, true)).thenReturn(Collections.emptyList());
        // emailExists calls searchByEmail which should return a non-empty list
        UserRepresentation existingUser = new UserRepresentation();
        existingUser.setEmail("existing@example.com");
        existingUser.setEnabled(true);
        when(usersResource.searchByEmail("existing@example.com", true)).thenReturn(List.of(existingUser));

        UserDTO userDTO = UserDTO.builder()
            .username("newuser")
            .email("existing@example.com")
            .build();

        // createUser catches all exceptions and rethrows as RuntimeException
        RuntimeException exception = assertThrows(RuntimeException.class, () -> 
            userService.createUser(userDTO, REALM));
        assertTrue(exception.getCause() instanceof IllegalArgumentException);
        assertTrue(exception.getCause().getMessage().contains("existe déjà"));
    }

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

        UserDTO userDTO = UserDTO.builder()
            .username("newuser")
            .email("test@example.com")
            .build();

        jakarta.ws.rs.core.Response response = mock(jakarta.ws.rs.core.Response.class);
        when(response.getStatus()).thenReturn(400);
        when(response.getStatusInfo()).thenReturn(jakarta.ws.rs.core.Response.Status.BAD_REQUEST);
        when(usersResource.create(any())).thenReturn(response);

        assertThrows(RuntimeException.class, () -> 
            userService.createUser(userDTO, REALM));
    }

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

        UserDTO userDTO = UserDTO.builder()
            .username("newuser")
            .email("test@example.com")
            .temporaryPassword("temp123")
            .temporaryPasswordFlag(true)
            .build();

        jakarta.ws.rs.core.Response response = mock(jakarta.ws.rs.core.Response.class);
        when(response.getStatus()).thenReturn(201);
        java.net.URI location = java.net.URI.create("http://localhost/users/" + USER_ID);
        when(response.getLocation()).thenReturn(location);
        when(usersResource.create(any())).thenReturn(response);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        
        UserRepresentation createdUser = new UserRepresentation();
        createdUser.setId(USER_ID);
        createdUser.setUsername("newuser");
        createdUser.setEnabled(true);
        when(userResource.toRepresentation()).thenReturn(createdUser);

        UserDTO result = userService.createUser(userDTO, REALM);

        assertNotNull(result);
        verify(userResource).resetPassword(any());
    }

    @Test
    void testCreateUser_Exception() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.search("newuser", 0, 1, true)).thenThrow(new RuntimeException("Connection error"));

        UserDTO userDTO = UserDTO.builder()
            .username("newuser")
            .email("test@example.com")
            .build();

        assertThrows(RuntimeException.class, () -> 
            userService.createUser(userDTO, REALM));
    }

    @Test
    void testUpdateUser_WithEmailAndPrenom() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        
        UserRepresentation existingUser = new UserRepresentation();
        existingUser.setId(USER_ID);
        existingUser.setUsername("testuser");
        existingUser.setEnabled(true);
        when(userResource.toRepresentation()).thenReturn(existingUser);

        UserDTO userDTO = UserDTO.builder()
            .id(USER_ID)
            .email("newemail@example.com")
            .prenom("John")
            .build();

        UserDTO result = userService.updateUser(USER_ID, userDTO, REALM);

        assertNotNull(result);
        verify(userResource).update(any(UserRepresentation.class));
    }

    @Test
    void testActivateUser_Success() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        
        UserRepresentation user = new UserRepresentation();
        user.setId(USER_ID);
        user.setEnabled(false);
        when(userResource.toRepresentation()).thenReturn(user);

        userService.activateUser(USER_ID, REALM);

        verify(userResource).update(any(UserRepresentation.class));
    }

    @Test
    void testActivateUser_Exception() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        when(userResource.toRepresentation()).thenThrow(new RuntimeException("Connection error"));

        assertThrows(RuntimeException.class, () -> 
            userService.activateUser(USER_ID, REALM));
    }

    @Test
    void testDeactivateUser_Success() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        
        UserRepresentation user = new UserRepresentation();
        user.setId(USER_ID);
        user.setEnabled(true);
        when(userResource.toRepresentation()).thenReturn(user);

        userService.deactivateUser(USER_ID, REALM, "Test reason");

        verify(userResource).update(any(UserRepresentation.class));
    }

    @Test
    void testDeactivateUser_Exception() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        when(userResource.toRepresentation()).thenThrow(new RuntimeException("Connection error"));

        assertThrows(RuntimeException.class, () -> 
            userService.deactivateUser(USER_ID, REALM, "Test reason"));
    }

    @Test
    void testSuspendUser() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        
        UserRepresentation user = new UserRepresentation();
        user.setId(USER_ID);
        user.setEnabled(true);
        when(userResource.toRepresentation()).thenReturn(user);

        userService.suspendUser(USER_ID, REALM, "Suspension reason", 30);

        verify(userResource).update(any(UserRepresentation.class));
    }

    @Test
    void testUnlockUser() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        
        UserRepresentation user = new UserRepresentation();
        user.setId(USER_ID);
        user.setEnabled(false);
        when(userResource.toRepresentation()).thenReturn(user);

        userService.unlockUser(USER_ID, REALM);

        verify(userResource).update(any(UserRepresentation.class));
    }

    @Test
    void testLogoutAllSessions_WithSessions() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        
        org.keycloak.representations.idm.UserSessionRepresentation session1 = 
            mock(org.keycloak.representations.idm.UserSessionRepresentation.class);
        org.keycloak.representations.idm.UserSessionRepresentation session2 = 
            mock(org.keycloak.representations.idm.UserSessionRepresentation.class);
        
        when(userResource.getUserSessions()).thenReturn(List.of(session1, session2));
        doNothing().when(userResource).logout();

        int count = userService.logoutAllSessions(USER_ID, REALM);

        assertEquals(2, count);
        verify(userResource).logout();
    }

    @Test
    void testLogoutAllSessions_NoSessions() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        when(userResource.getUserSessions()).thenReturn(Collections.emptyList());
        doNothing().when(userResource).logout();

        int count = userService.logoutAllSessions(USER_ID, REALM);

        assertEquals(0, count);
        verify(userResource).logout();
    }

    @Test
    void testLogoutAllSessions_Exception() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        when(userResource.getUserSessions()).thenThrow(new RuntimeException("Connection error"));

        assertThrows(RuntimeException.class, () -> 
            userService.logoutAllSessions(USER_ID, REALM));
    }

    @Test
    void testGetActiveSessions_Success() {
        when(keycloakAdminClient.getUsers(REALM)).thenReturn(usersResource);
        when(usersResource.get(USER_ID)).thenReturn(userResource);
        
        org.keycloak.representations.idm.UserSessionRepresentation session1 = 
            mock(org.keycloak.representations.idm.UserSessionRepresentation.class);
        org.keycloak.representations.idm.UserSessionRepresentation session2 = 
            mock(org.keycloak.representations.idm.UserSessionRepresentation.class);
        when(session1.getId()).thenReturn("session-1");
        when(session2.getId()).thenReturn("session-2");
        
        when(userResource.getUserSessions()).thenReturn(List.of(session1, session2));

        List<String> sessions = userService.getActiveSessions(USER_ID, REALM);

        assertNotNull(sessions);
        assertEquals(2, sessions.size());
        assertTrue(sessions.contains("session-1"));
        assertTrue(sessions.contains("session-2"));
    }
}

