package dev.lions.unionflow.server.security;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

import java.util.Set;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class SoDPermissionCheckerTest {

  private SoDPermissionChecker checker;
  private final UUID userA = UUID.randomUUID();
  private final UUID userB = UUID.randomUUID();
  private final UUID entityId = UUID.randomUUID();

  @BeforeEach
  void setUp() {
    checker = new SoDPermissionChecker();
    // AuditTrailService est injecté en prod ; on injecte un mock pour les tests.
    checker.auditTrail = mock(AuditTrailService.class);
  }

  @Test
  @DisplayName("checkValidationDistinct PASS si créateur ≠ validateur")
  void validationDistinctPass() {
    var result = checker.checkValidationDistinct(userA, userB, "Cotisation", entityId);
    assertThat(result.passed()).isTrue();
    assertThat(result.violationReason()).isNull();
  }

  @Test
  @DisplayName("checkValidationDistinct VIOLATION si même utilisateur")
  void validationDistinctViolation() {
    var result = checker.checkValidationDistinct(userA, userA, "Cotisation", entityId);
    assertThat(result.passed()).isFalse();
    assertThat(result.violationReason()).contains("SoD VIOLATION").contains("Cotisation");
  }

  @Test
  @DisplayName("checkValidationDistinct PASS si UUID null (contexte manquant)")
  void validationDistinctNullPass() {
    assertThat(checker.checkValidationDistinct(null, userA, "X", entityId).passed()).isTrue();
    assertThat(checker.checkValidationDistinct(userA, null, "X", entityId).passed()).isTrue();
  }

  @Test
  @DisplayName("checkRoleCombination : Trésorier + Président → VIOLATION")
  void cumulTresorierPresidentInterdit() {
    var result = checker.checkRoleCombination(userA, Set.of("TRESORIER", "PRESIDENT"));
    assertThat(result.passed()).isFalse();
    assertThat(result.violationReason()).contains("TRESORIER + PRESIDENT");
  }

  @Test
  @DisplayName("checkRoleCombination : Trésorier + Contrôleur Interne → VIOLATION (auto-contrôle)")
  void cumulTresorierControleurInterne() {
    var result = checker.checkRoleCombination(userA, Set.of("TRESORIER", "CONTROLEUR_INTERNE"));
    assertThat(result.passed()).isFalse();
    assertThat(result.violationReason()).contains("auto-contrôle");
  }

  @Test
  @DisplayName("checkRoleCombination : Président + Contrôleur Interne → VIOLATION (juge et partie)")
  void cumulPresidentControleur() {
    var result = checker.checkRoleCombination(userA, Set.of("PRESIDENT", "CONTROLEUR_INTERNE"));
    assertThat(result.passed()).isFalse();
    assertThat(result.violationReason()).contains("juge et partie");
  }

  @Test
  @DisplayName("checkRoleCombination : Commissaire aux comptes + autre → VIOLATION (indépendance OHADA)")
  void cumulCommissaireInterdit() {
    var result = checker.checkRoleCombination(userA, Set.of("COMMISSAIRE_COMPTES", "MEMBRE_ACTIF"));
    assertThat(result.passed()).isFalse();
    assertThat(result.violationReason()).contains("COMMISSAIRE_COMPTES");
    assertThat(result.violationReason()).contains("indépendant");
  }

  @Test
  @DisplayName("checkRoleCombination : un seul rôle → PASS")
  void unSeulRolePass() {
    assertThat(checker.checkRoleCombination(userA, Set.of("MEMBRE_ACTIF")).passed()).isTrue();
    assertThat(checker.checkRoleCombination(userA, Set.of("TRESORIER")).passed()).isTrue();
  }

  @Test
  @DisplayName("checkRoleCombination : combinaison autorisée → PASS")
  void combinaisonAutoriseePass() {
    assertThat(checker.checkRoleCombination(userA, Set.of("MEMBRE_ACTIF", "ANIMATEUR_ZONE")).passed())
        .isTrue();
    assertThat(checker.checkRoleCombination(userA, Set.of("SECRETAIRE", "SECRETAIRE_ADJOINT")).passed())
        .isTrue();
  }

  @Test
  @DisplayName("checkRoleCombination : null/empty → PASS")
  void roleCombinaisonNull() {
    assertThat(checker.checkRoleCombination(userA, null).passed()).isTrue();
    assertThat(checker.checkRoleCombination(userA, Set.of()).passed()).isTrue();
  }

  @Test
  @DisplayName("checkComplianceOfficerEligibility : Trésorier interdit (Instruction BCEAO 001-03-2025)")
  void complianceOfficerNotTresorier() {
    var result = checker.checkComplianceOfficerEligibility(
        userA, Set.of("TRESORIER", "MEMBRE_ACTIF"));
    assertThat(result.passed()).isFalse();
    assertThat(result.violationReason()).contains("TRESORIER");
  }

  @Test
  @DisplayName("checkComplianceOfficerEligibility : Commissaire aux comptes interdit (indépendance)")
  void complianceOfficerNotCommissaire() {
    var result = checker.checkComplianceOfficerEligibility(
        userA, Set.of("COMMISSAIRE_COMPTES"));
    assertThat(result.passed()).isFalse();
    assertThat(result.violationReason()).contains("COMMISSAIRE_COMPTES");
  }

  @Test
  @DisplayName("checkComplianceOfficerEligibility : ADMIN_ORGANISATION + MEMBRE → PASS")
  void complianceOfficerAdminMembrePass() {
    var result = checker.checkComplianceOfficerEligibility(
        userA, Set.of("ADMIN_ORGANISATION", "MEMBRE_ACTIF"));
    assertThat(result.passed()).isTrue();
  }
}
