package dev.lions.unionflow.server.security;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.Set;
import java.util.UUID;
import org.jboss.logging.Logger;

/**
 * Vérificateur de séparation des pouvoirs (Separation of Duties — SoD).
 *
 * <p>Implémente les règles SoD exigées par :
 *
 * <ul>
 *   <li><strong>SYSCOHADA / AUDCIF</strong> — traçabilité écritures comptables (chaque opération
 *       = pièce datée identifiable conservée), contrôle interne obligatoire ;
 *   <li><strong>AUDSCGIE OHADA</strong> — séparation CA décide / gérant exécute / Commissaire aux
 *       comptes contrôle ;
 *   <li><strong>BCEAO Circulaire 03-2017/CB/C</strong> — contrôle interne SFD UMOA (gouvernance,
 *       gestion risques, conformité, audit interne) ;
 *   <li><strong>Instruction BCEAO 001-03-2025</strong> — Compliance Officer rattaché DG, distinct
 *       trésorier/président.
 * </ul>
 *
 * <p>Les règles principales :
 *
 * <ol>
 *   <li>Un même utilisateur ne peut pas <strong>créer une dépense ET la valider</strong> (4-eyes
 *       principle pour engagement → ordonnancement → paiement).
 *   <li>Le <strong>Compliance Officer</strong> ne peut pas être simultanément trésorier ou
 *       président.
 *   <li>Un <strong>contrôleur interne</strong> ne peut pas être engagé dans des décisions
 *       opérationnelles qu'il devra contrôler.
 *   <li>Un membre exclu/radié ne peut plus exercer aucun rôle.
 * </ol>
 *
 * @since 2026-04-25
 */
@ApplicationScoped
public class SoDPermissionChecker {

  private static final Logger LOG = Logger.getLogger(SoDPermissionChecker.class);

  @Inject AuditTrailService auditTrail;

  /**
   * Vérifie qu'un utilisateur peut valider une opération qu'un autre a créée.
   *
   * <p>Règle fondamentale : <strong>celui qui crée ne peut pas valider</strong> (4-eyes).
   *
   * @param creatorUserId UUID du créateur de l'opération (de la dépense, du paiement, etc.)
   * @param validatorUserId UUID de l'utilisateur qui tente de valider
   * @param entityType nom de l'entité (pour audit trail)
   * @param entityId UUID de l'entité ciblée
   * @return {@link SoDCheckResult#PASS} si la validation est autorisée ; sinon {@link
   *     SoDCheckResult#VIOLATION}
   */
  public SoDCheckResult checkValidationDistinct(UUID creatorUserId, UUID validatorUserId,
                                                 String entityType, UUID entityId) {
    if (creatorUserId == null || validatorUserId == null) {
      return SoDCheckResult.PASS;  // pas de contexte = on laisse passer (audit doit alerter)
    }
    if (creatorUserId.equals(validatorUserId)) {
      String violation = String.format(
          "SoD VIOLATION: même utilisateur (%s) a créé ET valide cette opération sur %s/%s",
          validatorUserId, entityType, entityId);
      LOG.warn(violation);
      auditTrail.log(entityType, entityId, "SOD_OVERRIDE",
          "Tentative de validation par le créateur",
          null, null, null, false, violation);
      return new SoDCheckResult(false, violation);
    }
    return SoDCheckResult.PASS;
  }

  /**
   * Vérifie qu'un utilisateur n'a pas une combinaison de rôles incompatibles.
   *
   * <p>Combinaisons interdites par défaut :
   *
   * <ul>
   *   <li>{@code TRESORIER} + {@code PRESIDENT} (cumul interdit pour engagements financiers)
   *   <li>{@code TRESORIER} + {@code CONTROLEUR_INTERNE} (auto-contrôle impossible)
   *   <li>{@code PRESIDENT} + {@code CONTROLEUR_INTERNE} (juge et partie)
   *   <li>{@code COMMISSAIRE_COMPTES} + tout autre rôle interne (indépendance OHADA)
   * </ul>
   *
   * @param userId UUID de l'utilisateur
   * @param userRoles ensemble des rôles actuels de l'utilisateur dans l'organisation active
   * @return résultat de la vérification
   */
  public SoDCheckResult checkRoleCombination(UUID userId, Set<String> userRoles) {
    if (userRoles == null || userRoles.size() <= 1) {
      return SoDCheckResult.PASS;
    }

    // Conflit Commissaire aux comptes (indépendance absolue OHADA)
    if (userRoles.contains("COMMISSAIRE_COMPTES") && userRoles.size() > 1) {
      String violation = "SoD: COMMISSAIRE_COMPTES doit être indépendant (aucun cumul) — user " + userId;
      return new SoDCheckResult(false, violation);
    }

    // Conflits trésorier
    if (userRoles.contains("TRESORIER") && userRoles.contains("PRESIDENT")) {
      String violation = "SoD: cumul TRESORIER + PRESIDENT interdit (engagement + ordonnancement) — user " + userId;
      return new SoDCheckResult(false, violation);
    }
    if (userRoles.contains("TRESORIER") && userRoles.contains("CONTROLEUR_INTERNE")) {
      String violation = "SoD: cumul TRESORIER + CONTROLEUR_INTERNE interdit (auto-contrôle) — user " + userId;
      return new SoDCheckResult(false, violation);
    }
    if (userRoles.contains("PRESIDENT") && userRoles.contains("CONTROLEUR_INTERNE")) {
      String violation = "SoD: cumul PRESIDENT + CONTROLEUR_INTERNE interdit (juge et partie) — user " + userId;
      return new SoDCheckResult(false, violation);
    }

    return SoDCheckResult.PASS;
  }

  /**
   * Vérifie que le Compliance Officer désigné n'est pas en conflit (Instruction BCEAO
   * 001-03-2025 : rattaché DG, distinct du trésorier).
   */
  public SoDCheckResult checkComplianceOfficerEligibility(UUID complianceOfficerId, Set<String> userRoles) {
    if (complianceOfficerId == null || userRoles == null) {
      return SoDCheckResult.PASS;
    }
    if (userRoles.contains("TRESORIER")) {
      return new SoDCheckResult(false,
          "SoD: Compliance Officer ne peut pas cumuler le rôle TRESORIER (Instruction BCEAO 001-03-2025)");
    }
    if (userRoles.contains("COMMISSAIRE_COMPTES")) {
      return new SoDCheckResult(false,
          "SoD: Compliance Officer ne peut pas cumuler COMMISSAIRE_COMPTES (indépendance)");
    }
    return SoDCheckResult.PASS;
  }

  /** Résultat d'un check SoD : pass ou violation avec motif. */
  public record SoDCheckResult(boolean passed, String violationReason) {
    public static final SoDCheckResult PASS = new SoDCheckResult(true, null);

    public static final SoDCheckResult VIOLATION =
        new SoDCheckResult(false, "Violation SoD générique");

    public boolean isViolation() {
      return !passed;
    }
  }
}
