package dev.lions.unionflow.server.security;

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

import dev.lions.unionflow.server.service.KeycloakService;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@QuarkusTest
@DisplayName("SecurityConfig")
class SecurityConfigTest {

    @Inject SecurityConfig securityConfig;

    @Test
    @DisplayName("Roles constants are correct")
    void rolesConstants() {
        assertThat(SecurityConfig.Roles.ADMIN).isEqualTo("ADMIN");
        assertThat(SecurityConfig.Roles.GESTIONNAIRE_MEMBRE).isEqualTo("GESTIONNAIRE_MEMBRE");
        assertThat(SecurityConfig.Roles.TRESORIER).isEqualTo("TRESORIER");
        assertThat(SecurityConfig.Roles.SECRETAIRE).isEqualTo("SECRETAIRE");
        assertThat(SecurityConfig.Roles.MEMBRE).isEqualTo("MEMBRE");
        assertThat(SecurityConfig.Roles.PRESIDENT).isEqualTo("PRESIDENT");
        assertThat(SecurityConfig.Roles.VICE_PRESIDENT).isEqualTo("VICE_PRESIDENT");
        assertThat(SecurityConfig.Roles.ORGANISATEUR_EVENEMENT).isEqualTo("ORGANISATEUR_EVENEMENT");
        assertThat(SecurityConfig.Roles.GESTIONNAIRE_SOLIDARITE).isEqualTo("GESTIONNAIRE_SOLIDARITE");
        assertThat(SecurityConfig.Roles.AUDITEUR).isEqualTo("AUDITEUR");
    }

    @Test
    @DisplayName("Permissions constants are correct")
    void permissionsConstants() {
        assertThat(SecurityConfig.Permissions.CREATE_MEMBRE).isEqualTo("CREATE_MEMBRE");
        assertThat(SecurityConfig.Permissions.READ_MEMBRE).isEqualTo("READ_MEMBRE");
        assertThat(SecurityConfig.Permissions.UPDATE_MEMBRE).isEqualTo("UPDATE_MEMBRE");
        assertThat(SecurityConfig.Permissions.DELETE_MEMBRE).isEqualTo("DELETE_MEMBRE");
        assertThat(SecurityConfig.Permissions.CREATE_ORGANISATION).isEqualTo("CREATE_ORGANISATION");
        assertThat(SecurityConfig.Permissions.READ_ORGANISATION).isEqualTo("READ_ORGANISATION");
        assertThat(SecurityConfig.Permissions.UPDATE_ORGANISATION).isEqualTo("UPDATE_ORGANISATION");
        assertThat(SecurityConfig.Permissions.DELETE_ORGANISATION).isEqualTo("DELETE_ORGANISATION");
        assertThat(SecurityConfig.Permissions.CREATE_EVENEMENT).isEqualTo("CREATE_EVENEMENT");
        assertThat(SecurityConfig.Permissions.READ_EVENEMENT).isEqualTo("READ_EVENEMENT");
        assertThat(SecurityConfig.Permissions.UPDATE_EVENEMENT).isEqualTo("UPDATE_EVENEMENT");
        assertThat(SecurityConfig.Permissions.DELETE_EVENEMENT).isEqualTo("DELETE_EVENEMENT");
        assertThat(SecurityConfig.Permissions.CREATE_COTISATION).isEqualTo("CREATE_COTISATION");
        assertThat(SecurityConfig.Permissions.READ_COTISATION).isEqualTo("READ_COTISATION");
        assertThat(SecurityConfig.Permissions.UPDATE_COTISATION).isEqualTo("UPDATE_COTISATION");
        assertThat(SecurityConfig.Permissions.DELETE_COTISATION).isEqualTo("DELETE_COTISATION");
        assertThat(SecurityConfig.Permissions.CREATE_SOLIDARITE).isEqualTo("CREATE_SOLIDARITE");
        assertThat(SecurityConfig.Permissions.READ_SOLIDARITE).isEqualTo("READ_SOLIDARITE");
        assertThat(SecurityConfig.Permissions.UPDATE_SOLIDARITE).isEqualTo("UPDATE_SOLIDARITE");
        assertThat(SecurityConfig.Permissions.DELETE_SOLIDARITE).isEqualTo("DELETE_SOLIDARITE");
        assertThat(SecurityConfig.Permissions.ADMIN_USERS).isEqualTo("ADMIN_USERS");
        assertThat(SecurityConfig.Permissions.ADMIN_SYSTEM).isEqualTo("ADMIN_SYSTEM");
        assertThat(SecurityConfig.Permissions.VIEW_REPORTS).isEqualTo("VIEW_REPORTS");
        assertThat(SecurityConfig.Permissions.EXPORT_DATA).isEqualTo("EXPORT_DATA");
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("hasRole returns true for ADMIN")
    void hasRole_admin_returnsTrue() {
        assertThat(securityConfig.hasRole("ADMIN")).isTrue();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("hasRole returns false for TRESORIER when user is ADMIN only")
    void hasRole_tresorier_returnsFalse() {
        assertThat(securityConfig.hasRole("TRESORIER")).isFalse();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN", "TRESORIER"})
    @DisplayName("hasAnyRole returns true when user has one of the roles")
    void hasAnyRole_returnsTrue() {
        assertThat(securityConfig.hasAnyRole("ADMIN", "MEMBRE")).isTrue();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN", "TRESORIER"})
    @DisplayName("hasAllRoles returns true when user has all roles")
    void hasAllRoles_returnsTrue() {
        assertThat(securityConfig.hasAllRoles("ADMIN", "TRESORIER")).isTrue();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN", "TRESORIER"})
    @DisplayName("hasAllRoles returns false when missing a role")
    void hasAllRoles_missing_returnsFalse() {
        assertThat(securityConfig.hasAllRoles("ADMIN", "MEMBRE")).isFalse();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("getCurrentUserId does not throw when authenticated")
    void getCurrentUserId() {
        // With @TestSecurity, JWT claims (sub) may not be set, so result can be null
        securityConfig.getCurrentUserId();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("getCurrentUserEmail does not throw when authenticated")
    void getCurrentUserEmail() {
        // With @TestSecurity, JWT claims (email) may not be set, so result can be null
        securityConfig.getCurrentUserEmail();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("getCurrentUserRoles returns non-empty set")
    void getCurrentUserRoles() {
        assertThat(securityConfig.getCurrentUserRoles()).isNotEmpty();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("isAuthenticated returns true when authenticated")
    void isAuthenticated_returnsTrue() {
        assertThat(securityConfig.isAuthenticated()).isTrue();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("isAdmin returns true for ADMIN role")
    void isAdmin_returnsTrue() {
        assertThat(securityConfig.isAdmin()).isTrue();
    }

    @Test
    @TestSecurity(user = "user@test.com", roles = {"MEMBRE"})
    @DisplayName("isAdmin returns false for MEMBRE role")
    void isAdmin_returnsFalse() {
        assertThat(securityConfig.isAdmin()).isFalse();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("canManageMembers returns true for ADMIN")
    void canManageMembers_admin() {
        assertThat(securityConfig.canManageMembers()).isTrue();
    }

    @Test
    @TestSecurity(user = "gest@test.com", roles = {"GESTIONNAIRE_MEMBRE"})
    @DisplayName("canManageMembers returns true for GESTIONNAIRE_MEMBRE")
    void canManageMembers_gestionnaire() {
        assertThat(securityConfig.canManageMembers()).isTrue();
    }

    @Test
    @TestSecurity(user = "user@test.com", roles = {"MEMBRE"})
    @DisplayName("canManageMembers returns false for MEMBRE")
    void canManageMembers_membre_returnsFalse() {
        assertThat(securityConfig.canManageMembers()).isFalse();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("canManageFinances returns true for ADMIN")
    void canManageFinances_admin() {
        assertThat(securityConfig.canManageFinances()).isTrue();
    }

    @Test
    @TestSecurity(user = "tres@test.com", roles = {"TRESORIER"})
    @DisplayName("canManageFinances returns true for TRESORIER")
    void canManageFinances_tresorier() {
        assertThat(securityConfig.canManageFinances()).isTrue();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("canManageEvents returns true for ADMIN")
    void canManageEvents_admin() {
        assertThat(securityConfig.canManageEvents()).isTrue();
    }

    @Test
    @TestSecurity(user = "org@test.com", roles = {"ORGANISATEUR_EVENEMENT"})
    @DisplayName("canManageEvents returns true for ORGANISATEUR_EVENEMENT")
    void canManageEvents_organisateur() {
        assertThat(securityConfig.canManageEvents()).isTrue();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("canManageOrganizations returns true for ADMIN")
    void canManageOrganizations_admin() {
        assertThat(securityConfig.canManageOrganizations()).isTrue();
    }

    @Test
    @TestSecurity(user = "pres@test.com", roles = {"PRESIDENT"})
    @DisplayName("canManageOrganizations returns true for PRESIDENT")
    void canManageOrganizations_president() {
        assertThat(securityConfig.canManageOrganizations()).isTrue();
    }

    @Test
    @TestSecurity(user = "user@test.com", roles = {"MEMBRE"})
    @DisplayName("canManageOrganizations returns false for MEMBRE")
    void canManageOrganizations_membre_returnsFalse() {
        assertThat(securityConfig.canManageOrganizations()).isFalse();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("canAccessMemberData returns true for ADMIN accessing any data")
    void canAccessMemberData_admin() {
        assertThat(securityConfig.canAccessMemberData("some-user-id")).isTrue();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("canAccessMemberData returns true when accessing own data (même userId)")
    void canAccessMemberData_ownData_returnsTrue() {
        String currentId = securityConfig.getCurrentUserId();
        if (currentId != null && !currentId.isEmpty()) {
            assertThat(securityConfig.canAccessMemberData(currentId)).isTrue();
        }
        // Si getCurrentUserId() retourne null (contexte test), tester avec un id arbitraire
        assertThat(securityConfig.canAccessMemberData("other-user-id")).isTrue();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("canAccessMemberData returns true for ADMIN accessing other data")
    void canAccessMemberData_adminOther() {
        assertThat(securityConfig.canAccessMemberData("other-user-id")).isTrue();
    }

    @Test
    @TestSecurity(user = "user@test.com", roles = {"MEMBRE"})
    @DisplayName("canAccessMemberData returns false for MEMBRE accessing other data")
    void canAccessMemberData_membreOther_returnsFalse() {
        assertThat(securityConfig.canAccessMemberData("other-user-id")).isFalse();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("logSecurityInfo does not throw when authenticated")
    void logSecurityInfo_authenticated() {
        securityConfig.logSecurityInfo();
    }

    @Test
    @DisplayName("logSecurityInfo does not throw when not authenticated")
    void logSecurityInfo_notAuthenticated() {
        securityConfig.logSecurityInfo();
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("logSecurityInfo isAuthenticated=true branche dans debug — couvre inner if true (missed branch)")
    void logSecurityInfo_authenticatedDebugBranch() {
        // With DEBUG level enabled for SecurityConfig in test application.properties,
        // LOG.isDebugEnabled() = true AND isAuthenticated() = true (via @TestSecurity)
        // → covers the inner "if (isAuthenticated())" true branch
        securityConfig.logSecurityInfo();
    }

    @Test
    @DisplayName("logSecurityInfo isAuthenticated=false branche dans debug — couvre inner else (missed branch)")
    void logSecurityInfo_notAuthenticatedDebugBranch() {
        // With DEBUG level enabled for SecurityConfig in test application.properties,
        // LOG.isDebugEnabled() = true AND isAuthenticated() = false (no @TestSecurity)
        // → covers the inner "else" branch (LOG.debug("Utilisateur non authentifié"))
        securityConfig.logSecurityInfo();
    }

    @Test
    @DisplayName("SecurityConfig.Roles constructor peut être instancié")
    void roles_canBeInstantiated() {
        SecurityConfig.Roles roles = new SecurityConfig.Roles();
        assertThat(roles).isNotNull();
    }

    @Test
    @DisplayName("SecurityConfig.Permissions constructor peut être instancié")
    void permissions_canBeInstantiated() {
        SecurityConfig.Permissions perms = new SecurityConfig.Permissions();
        assertThat(perms).isNotNull();
    }
}
