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

import dev.lions.user.manager.client.KeycloakAdminClient;
import dev.lions.user.manager.dto.role.RoleDTO;
import dev.lions.user.manager.enums.role.TypeRole;
import dev.lions.user.manager.mapper.RoleMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.*;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

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

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

/**
 * Tests complets pour RoleServiceImpl pour atteindre 100% de couverture
 * Couvre updateRole, deleteRole pour CLIENT_ROLE, createRealmRole avec rôle existant, etc.
 */
@ExtendWith(MockitoExtension.class)
class RoleServiceImplCompleteTest {

    @Mock
    private KeycloakAdminClient keycloakAdminClient;

    @Mock
    private Keycloak keycloakInstance;

    @Mock
    private RealmResource realmResource;

    @Mock
    private RolesResource rolesResource;

    @Mock
    private RoleResource roleResource;

    @Mock
    private ClientsResource clientsResource;

    @Mock
    private ClientResource clientResource;

    @InjectMocks
    private RoleServiceImpl roleService;

    private static final String REALM = "test-realm";
    private static final String ROLE_ID = "role-123";
    private static final String ROLE_NAME = "test-role";
    private static final String CLIENT_NAME = "test-client";
    private static final String INTERNAL_CLIENT_ID = "internal-client-id";

    @Test
    void testCreateRealmRole_RoleAlreadyExists() {
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.roles()).thenReturn(rolesResource);
        when(rolesResource.get(ROLE_NAME)).thenReturn(roleResource);
        
        RoleRepresentation existingRole = new RoleRepresentation();
        existingRole.setName(ROLE_NAME);
        when(roleResource.toRepresentation()).thenReturn(existingRole);

        RoleDTO roleDTO = RoleDTO.builder()
            .name(ROLE_NAME)
            .description("Test role")
            .build();

        assertThrows(IllegalArgumentException.class, () -> 
            roleService.createRealmRole(roleDTO, REALM));
    }

    @Test
    void testUpdateRole_RealmRole_Success() {
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.roles()).thenReturn(rolesResource);
        
        // Mock getRealmRoleById
        RoleRepresentation roleRep = new RoleRepresentation();
        roleRep.setId(ROLE_ID);
        roleRep.setName(ROLE_NAME);
        when(rolesResource.list()).thenReturn(List.of(roleRep));
        when(rolesResource.get(ROLE_NAME)).thenReturn(roleResource);
        when(roleResource.toRepresentation()).thenReturn(roleRep);

        RoleDTO roleDTO = RoleDTO.builder()
            .id(ROLE_ID)
            .name(ROLE_NAME)
            .description("Updated description")
            .build();

        RoleDTO result = roleService.updateRole(ROLE_ID, roleDTO, REALM, TypeRole.REALM_ROLE, null);

        assertNotNull(result);
        verify(roleResource).update(any(RoleRepresentation.class));
    }

    @Test
    void testUpdateRole_RealmRole_NotFound() {
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.roles()).thenReturn(rolesResource);
        when(rolesResource.list()).thenReturn(Collections.emptyList());

        RoleDTO roleDTO = RoleDTO.builder()
            .id(ROLE_ID)
            .name(ROLE_NAME)
            .build();

        assertThrows(jakarta.ws.rs.NotFoundException.class, () -> 
            roleService.updateRole(ROLE_ID, roleDTO, REALM, TypeRole.REALM_ROLE, null));
    }

    @Test
    void testUpdateRole_RealmRole_NoDescription() {
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.roles()).thenReturn(rolesResource);
        
        RoleRepresentation roleRep = new RoleRepresentation();
        roleRep.setId(ROLE_ID);
        roleRep.setName(ROLE_NAME);
        when(rolesResource.list()).thenReturn(List.of(roleRep));
        when(rolesResource.get(ROLE_NAME)).thenReturn(roleResource);
        when(roleResource.toRepresentation()).thenReturn(roleRep);

        RoleDTO roleDTO = RoleDTO.builder()
            .id(ROLE_ID)
            .name(ROLE_NAME)
            .description(null) // No description
            .build();

        RoleDTO result = roleService.updateRole(ROLE_ID, roleDTO, REALM, TypeRole.REALM_ROLE, null);

        assertNotNull(result);
        verify(roleResource).update(any(RoleRepresentation.class));
    }

    @Test
    void testUpdateRole_ClientRole_Success() {
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.clients()).thenReturn(clientsResource);
        
        ClientRepresentation client = new ClientRepresentation();
        client.setId(INTERNAL_CLIENT_ID);
        when(clientsResource.findByClientId(CLIENT_NAME)).thenReturn(List.of(client));
        when(clientsResource.get(INTERNAL_CLIENT_ID)).thenReturn(clientResource);
        when(clientResource.roles()).thenReturn(rolesResource);
        
        // Mock getRoleById
        RoleRepresentation roleRep = new RoleRepresentation();
        roleRep.setId(ROLE_ID);
        roleRep.setName(ROLE_NAME);
        when(rolesResource.list()).thenReturn(List.of(roleRep));
        when(rolesResource.get(ROLE_NAME)).thenReturn(roleResource);
        when(roleResource.toRepresentation()).thenReturn(roleRep);

        RoleDTO roleDTO = RoleDTO.builder()
            .id(ROLE_ID)
            .name(ROLE_NAME)
            .description("Updated description")
            .build();

        RoleDTO result = roleService.updateRole(ROLE_ID, roleDTO, REALM, TypeRole.CLIENT_ROLE, CLIENT_NAME);

        assertNotNull(result);
        assertEquals(CLIENT_NAME, result.getClientId());
        verify(roleResource).update(any(RoleRepresentation.class));
    }

    @Test
    void testUpdateRole_ClientRole_ClientNotFound() {
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.clients()).thenReturn(clientsResource);
        when(clientsResource.findByClientId(CLIENT_NAME)).thenReturn(Collections.emptyList());

        RoleDTO roleDTO = RoleDTO.builder()
            .id(ROLE_ID)
            .name(ROLE_NAME)
            .build();

        // getRoleById is called first, which will throw NotFoundException when client is not found
        // Actually, getRoleById returns Optional.empty() when client is not found
        // So it will throw NotFoundException for role not found
        assertThrows(jakarta.ws.rs.NotFoundException.class, () -> 
            roleService.updateRole(ROLE_ID, roleDTO, REALM, TypeRole.CLIENT_ROLE, CLIENT_NAME));
    }

    @Test
    void testUpdateRole_ClientRole_NotFound() {
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.clients()).thenReturn(clientsResource);
        
        ClientRepresentation client = new ClientRepresentation();
        client.setId(INTERNAL_CLIENT_ID);
        when(clientsResource.findByClientId(CLIENT_NAME)).thenReturn(List.of(client));
        when(clientsResource.get(INTERNAL_CLIENT_ID)).thenReturn(clientResource);
        when(clientResource.roles()).thenReturn(rolesResource);
        when(rolesResource.list()).thenReturn(Collections.emptyList());

        RoleDTO roleDTO = RoleDTO.builder()
            .id(ROLE_ID)
            .name(ROLE_NAME)
            .build();

        assertThrows(jakarta.ws.rs.NotFoundException.class, () -> 
            roleService.updateRole(ROLE_ID, roleDTO, REALM, TypeRole.CLIENT_ROLE, CLIENT_NAME));
    }

    @Test
    void testUpdateRole_UnsupportedType() {
        RoleDTO roleDTO = RoleDTO.builder()
            .id(ROLE_ID)
            .name(ROLE_NAME)
            .build();

        assertThrows(IllegalArgumentException.class, () -> 
            roleService.updateRole(ROLE_ID, roleDTO, REALM, null, null));
    }

    @Test
    void testDeleteRole_ClientRole_Success() {
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.clients()).thenReturn(clientsResource);
        
        ClientRepresentation client = new ClientRepresentation();
        client.setId(INTERNAL_CLIENT_ID);
        when(clientsResource.findByClientId(CLIENT_NAME)).thenReturn(List.of(client));
        when(clientsResource.get(INTERNAL_CLIENT_ID)).thenReturn(clientResource);
        when(clientResource.roles()).thenReturn(rolesResource);
        
        // Mock getRoleById - getRoleById for CLIENT_ROLE only uses rolesResource.list()
        RoleRepresentation roleRep = new RoleRepresentation();
        roleRep.setId(ROLE_ID);
        roleRep.setName(ROLE_NAME);
        when(rolesResource.list()).thenReturn(List.of(roleRep));

        roleService.deleteRole(ROLE_ID, REALM, TypeRole.CLIENT_ROLE, CLIENT_NAME);

        verify(rolesResource).deleteRole(ROLE_NAME);
    }

    @Test
    void testDeleteRole_ClientRole_ClientNotFound() {
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.clients()).thenReturn(clientsResource);
        when(clientsResource.findByClientId(CLIENT_NAME)).thenReturn(Collections.emptyList());

        // getRoleById is called first, which returns Optional.empty() when client is not found
        // So it will throw NotFoundException for role not found
        assertThrows(jakarta.ws.rs.NotFoundException.class, () -> 
            roleService.deleteRole(ROLE_ID, REALM, TypeRole.CLIENT_ROLE, CLIENT_NAME));
    }

    @Test
    void testDeleteRole_ClientRole_NotFound() {
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.clients()).thenReturn(clientsResource);
        
        ClientRepresentation client = new ClientRepresentation();
        client.setId(INTERNAL_CLIENT_ID);
        when(clientsResource.findByClientId(CLIENT_NAME)).thenReturn(List.of(client));
        when(clientsResource.get(INTERNAL_CLIENT_ID)).thenReturn(clientResource);
        when(clientResource.roles()).thenReturn(rolesResource);
        when(rolesResource.list()).thenReturn(Collections.emptyList());

        assertThrows(jakarta.ws.rs.NotFoundException.class, () -> 
            roleService.deleteRole(ROLE_ID, REALM, TypeRole.CLIENT_ROLE, CLIENT_NAME));
    }

    @Test
    void testDeleteRole_UnsupportedType() {
        assertThrows(IllegalArgumentException.class, () -> 
            roleService.deleteRole(ROLE_ID, REALM, null, null));
    }

    // Note: getRealmRoleById is private, so we test it indirectly through updateRole
    // The exception path is tested via updateRole_RealmRole_NotFound

    @Test
    void testGetAllRealmRoles_Success() {
        when(keycloakAdminClient.realmExists(REALM)).thenReturn(true);
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.roles()).thenReturn(rolesResource);
        
        RoleRepresentation role1 = new RoleRepresentation();
        role1.setName("role1");
        RoleRepresentation role2 = new RoleRepresentation();
        role2.setName("role2");
        when(rolesResource.list()).thenReturn(List.of(role1, role2));

        var result = roleService.getAllRealmRoles(REALM);

        assertNotNull(result);
        assertEquals(2, result.size());
    }

    @Test
    void testGetAllRealmRoles_With404InMessage() {
        when(keycloakAdminClient.realmExists(REALM)).thenReturn(true);
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.roles()).thenReturn(rolesResource);
        when(rolesResource.list()).thenThrow(new RuntimeException("Server response is: 404"));

        assertThrows(IllegalArgumentException.class, () -> 
            roleService.getAllRealmRoles(REALM));
    }

    @Test
    void testGetAllRealmRoles_WithNotInMessage() {
        when(keycloakAdminClient.realmExists(REALM)).thenReturn(true);
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.roles()).thenReturn(rolesResource);
        when(rolesResource.list()).thenThrow(new RuntimeException("Not Found"));

        assertThrows(IllegalArgumentException.class, () -> 
            roleService.getAllRealmRoles(REALM));
    }

    @Test
    void testGetAllRealmRoles_WithOtherException() {
        when(keycloakAdminClient.realmExists(REALM)).thenReturn(true);
        when(keycloakAdminClient.getInstance()).thenReturn(keycloakInstance);
        when(keycloakInstance.realm(REALM)).thenReturn(realmResource);
        when(realmResource.roles()).thenReturn(rolesResource);
        when(rolesResource.list()).thenThrow(new RuntimeException("Connection error"));

        assertThrows(RuntimeException.class, () -> 
            roleService.getAllRealmRoles(REALM));
    }
}

