package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.entity.Role;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.TestTransaction;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

@QuarkusTest
class RoleServiceTest {

    @Inject
    RoleService roleService;

    @Test
    @TestTransaction
    @DisplayName("trouverParId avec UUID inexistant retourne null")
    void trouverParId_inexistant_returnsNull() {
        Role found = roleService.trouverParId(UUID.randomUUID());
        assertThat(found).isNull();
    }

    @Test
    @TestTransaction
    @DisplayName("trouverParCode avec code inexistant retourne null")
    void trouverParCode_inexistant_returnsNull() {
        Role found = roleService.trouverParCode("CODE_INEXISTANT_" + UUID.randomUUID());
        assertThat(found).isNull();
    }

    @Test
    @TestTransaction
    @DisplayName("listerRolesSysteme retourne une liste")
    void listerRolesSysteme_returnsList() {
        List<Role> list = roleService.listerRolesSysteme();
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("listerTousActifs retourne une liste")
    void listerTousActifs_returnsList() {
        List<Role> list = roleService.listerTousActifs();
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("listerParOrganisation retourne une liste")
    void listerParOrganisation_returnsList() {
        List<Role> list = roleService.listerParOrganisation(UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("creerRole avec code unique crée le rôle")
    void creerRole_codeUnique_createsRole() {
        String code = "ROLE_TEST_" + UUID.randomUUID().toString().substring(0, 8);
        Role role = Role.builder()
                .code(code)
                .libelle("Rôle test service")
                .typeRole(Role.TypeRole.PERSONNALISE.name())
                .niveauHierarchique(100)
                .build();
        Role created = roleService.creerRole(role);
        assertThat(created).isNotNull();
        assertThat(created.getId()).isNotNull();
        assertThat(created.getCode()).isEqualTo(code);
    }

    @Test
    @TestTransaction
    @DisplayName("creerRole avec code déjà existant lance IllegalArgumentException")
    void creerRole_codeExistant_throws() {
        String code = "ROLE_DUP_" + UUID.randomUUID().toString().substring(0, 8);
        Role role = Role.builder()
                .code(code)
                .libelle("Premier")
                .typeRole(Role.TypeRole.PERSONNALISE.name())
                .niveauHierarchique(100)
                .build();
        roleService.creerRole(role);
        Role duplicate = Role.builder()
                .code(code)
                .libelle("Duplicate")
                .typeRole(Role.TypeRole.PERSONNALISE.name())
                .niveauHierarchique(100)
                .build();
        assertThatThrownBy(() -> roleService.creerRole(duplicate))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("existe déjà");
    }

    @Test
    @TestTransaction
    @DisplayName("mettreAJourRole avec ID inexistant lance NotFoundException")
    void mettreAJourRole_inexistant_throwsNotFound() {
        Role modification = Role.builder()
                .code("ROLE_MOD_" + UUID.randomUUID().toString().substring(0, 8))
                .libelle("Modifié")
                .typeRole(Role.TypeRole.PERSONNALISE.name())
                .niveauHierarchique(100)
                .build();
        assertThatThrownBy(() -> roleService.mettreAJourRole(UUID.randomUUID(), modification))
                .isInstanceOf(jakarta.ws.rs.NotFoundException.class);
    }

    @Test
    @TestTransaction
    @DisplayName("mettreAJourRole avec même code ne vérifie pas l'unicité")
    void mettreAJourRole_sameCode_updatesRole() {
        String code = "ROLE_UPDT_" + UUID.randomUUID().toString().substring(0, 8);
        Role role = Role.builder()
                .code(code)
                .libelle("Avant mise à jour")
                .typeRole(Role.TypeRole.PERSONNALISE.name())
                .niveauHierarchique(100)
                .build();
        Role created = roleService.creerRole(role);

        Role modification = Role.builder()
                .code(code) // même code
                .libelle("Après mise à jour")
                .typeRole(Role.TypeRole.PERSONNALISE.name())
                .niveauHierarchique(50)
                .build();
        Role updated = roleService.mettreAJourRole(created.getId(), modification);
        assertThat(updated.getLibelle()).isEqualTo("Après mise à jour");
        assertThat(updated.getNiveauHierarchique()).isEqualTo(50);
    }

    @Test
    @TestTransaction
    @DisplayName("mettreAJourRole avec code déjà pris par un autre rôle lance IllegalArgumentException")
    void mettreAJourRole_codeDejaExistant_throws() {
        String codeA = "ROLE_A_" + UUID.randomUUID().toString().substring(0, 8);
        String codeB = "ROLE_B_" + UUID.randomUUID().toString().substring(0, 8);
        Role roleA = Role.builder()
                .code(codeA).libelle("A").typeRole(Role.TypeRole.PERSONNALISE.name()).niveauHierarchique(100).build();
        Role roleB = Role.builder()
                .code(codeB).libelle("B").typeRole(Role.TypeRole.PERSONNALISE.name()).niveauHierarchique(100).build();
        roleService.creerRole(roleA);
        Role createdB = roleService.creerRole(roleB);

        Role modification = Role.builder()
                .code(codeA) // code déjà pris par roleA
                .libelle("B modifié")
                .typeRole(Role.TypeRole.PERSONNALISE.name())
                .niveauHierarchique(100)
                .build();
        assertThatThrownBy(() -> roleService.mettreAJourRole(createdB.getId(), modification))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("existe déjà");
    }

    @Test
    @TestTransaction
    @DisplayName("supprimerRole avec ID inexistant lance NotFoundException")
    void supprimerRole_inexistant_throwsNotFound() {
        assertThatThrownBy(() -> roleService.supprimerRole(UUID.randomUUID()))
                .isInstanceOf(jakarta.ws.rs.NotFoundException.class);
    }

    @Test
    @TestTransaction
    @DisplayName("supprimerRole sur un rôle système lance IllegalStateException")
    void supprimerRole_roleSysteme_throwsIllegalState() {
        String code = "SYS_ROLE_" + UUID.randomUUID().toString().substring(0, 8);
        Role role = Role.builder()
                .code(code)
                .libelle("Rôle système")
                .typeRole(Role.TypeRole.SYSTEME.name())
                .niveauHierarchique(1)
                .build();
        Role created = roleService.creerRole(role);
        assertThatThrownBy(() -> roleService.supprimerRole(created.getId()))
                .isInstanceOf(IllegalStateException.class)
                .hasMessageContaining("système");
    }

    @Test
    @TestTransaction
    @DisplayName("mettreAJourRole avec code différent mais disponible → mise à jour réussie (L76 false)")
    void mettreAJourRole_codeDifferentDisponible_updatesRole() {
        // Couvre L76 false: code differ + new code NOT taken → update succeeds
        String ancienCode = "ROLE_OLD_" + UUID.randomUUID().toString().substring(0, 8);
        String nouveauCode = "ROLE_NEW_" + UUID.randomUUID().toString().substring(0, 8);
        Role role = Role.builder()
                .code(ancienCode)
                .libelle("Ancien libellé")
                .typeRole(Role.TypeRole.PERSONNALISE.name())
                .niveauHierarchique(100)
                .build();
        Role created = roleService.creerRole(role);

        // Changer le code vers un code qui n'existe pas encore → inner if (L76) false → continue
        Role modification = Role.builder()
                .code(nouveauCode) // code différent ET non existant
                .libelle("Nouveau libellé")
                .typeRole(Role.TypeRole.PERSONNALISE.name())
                .niveauHierarchique(200)
                .build();
        Role updated = roleService.mettreAJourRole(created.getId(), modification);
        assertThat(updated.getCode()).isEqualTo(nouveauCode);
        assertThat(updated.getLibelle()).isEqualTo("Nouveau libellé");
    }

    @Test
    @TestTransaction
    @DisplayName("supprimerRole désactive un rôle non-système")
    void supprimerRole_nonSysteme_desactivesRole() {
        String code = "ROLE_DEL_" + UUID.randomUUID().toString().substring(0, 8);
        Role role = Role.builder()
                .code(code)
                .libelle("À supprimer")
                .typeRole(Role.TypeRole.PERSONNALISE.name())
                .niveauHierarchique(100)
                .build();
        Role created = roleService.creerRole(role);
        roleService.supprimerRole(created.getId());
        // Le rôle est désactivé, findRoleById ne le trouve plus (actif = true)
        Role found = roleService.trouverParId(created.getId());
        assertThat(found).isNull();
    }
}
