package dev.lions.unionflow.server.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import dev.lions.unionflow.server.entity.AuditTrailOperation;
import dev.lions.unionflow.server.repository.AuditTrailOperationRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.time.LocalDateTime;
import java.util.UUID;
import org.jboss.logging.Logger;

/**
 * Service d'audit trail enrichi (SYSCOHADA + AUDSCGIE OHADA + Instruction BCEAO 003-03-2025).
 *
 * <p>Enregistre toutes les opérations sensibles (financières, lifecycle membres, configurations)
 * avec leur contexte multi-org complet (rôle actif, organisation active, vérifications SoD).
 *
 * <p>Usage typique dans un service métier :
 *
 * <pre>{@code
 *   @Inject AuditTrailService auditTrail;
 *
 *   public Cotisation enregistrerPaiement(...) {
 *       Cotisation c = ...
 *       auditTrail.log("Cotisation", c.getId(), "PAYMENT_CONFIRMED",
 *                      "Paiement confirmé via " + provider, c);
 *       return c;
 *   }
 * }</pre>
 *
 * @since 2026-04-25
 */
@ApplicationScoped
public class AuditTrailService {

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

  @Inject AuditTrailOperationRepository repository;
  @Inject OrganisationContextHolder context;
  @Inject io.micrometer.core.instrument.MeterRegistry registry;
  @Inject jakarta.enterprise.event.Event<AuditOperationLoggedEvent> auditEvent;

  private final ObjectMapper objectMapper = new ObjectMapper();

  /**
   * Enregistre une entrée d'audit trail à partir du contexte courant.
   *
   * @param entityType nom de l'entité (ex: "Cotisation", "Membre", "EcritureComptable")
   * @param entityId UUID de l'entité ciblée (peut être null pour actions globales)
   * @param actionType type d'action (cf. CHECK SQL : CREATE, UPDATE, DELETE, APPROVE, ...)
   * @param description description libre courte (≤ 500 caractères)
   * @param payloadApres entité après modification (sérialisée JSON, peut être null)
   */
  @Transactional
  public void log(String entityType, UUID entityId, String actionType, String description,
                  Object payloadApres) {
    log(entityType, entityId, actionType, description, null, payloadApres, null, null, null);
  }

  /**
   * Enregistre une entrée d'audit trail avec snapshot avant/après et résultat SoD.
   */
  @Transactional
  public void log(String entityType, UUID entityId, String actionType, String description,
                  Object payloadAvant, Object payloadApres, Object metadata,
                  Boolean sodCheckPassed, String sodViolations) {
    try {
      AuditTrailOperation entry = AuditTrailOperation.builder()
          .userId(context.getCurrentUserId())
          .userEmail(context.getCurrentUserEmail())
          .roleActif(context.getRoleActif())
          .organisationActiveId(context.getOrganisationId())
          .actionType(actionType)
          .entityType(entityType)
          .entityId(entityId)
          .description(description)
          .payloadAvant(toJson(payloadAvant))
          .payloadApres(toJson(payloadApres))
          .metadata(toJson(metadata))
          .sodCheckPassed(sodCheckPassed)
          .sodViolations(sodViolations)
          .operationAt(LocalDateTime.now())
          .build();
      repository.persist(entry);

      // Sprint 16.A — Métriques Prometheus métier (transparency)
      registry.counter("unionflow.audit.operations",
          "action", actionType == null ? "UNKNOWN" : actionType,
          "entity", entityType == null ? "UNKNOWN" : entityType).increment();
      if (Boolean.FALSE.equals(sodCheckPassed)) {
        registry.counter("unionflow.audit.sod_violations",
            "entity", entityType == null ? "UNKNOWN" : entityType).increment();
      }

      // Sprint 16.C — Fire CDI event pour observers (notifications, etc.)
      auditEvent.fire(new AuditOperationLoggedEvent(
          entry.getId(), entry.getUserId(), entry.getUserEmail(),
          entry.getOrganisationActiveId(), actionType, entityType, entityId,
          description, sodCheckPassed, entry.getOperationAt()));
    } catch (Exception e) {
      // Fail-soft : l'audit trail ne doit jamais bloquer une opération métier.
      // Les violations sont loguées et peuvent être détectées via les logs applicatifs.
      LOG.errorf(e,
          "Audit trail log failed: entityType=%s entityId=%s actionType=%s description=%s",
          entityType, entityId, actionType, description);
    }
  }

  /** Variante sans payload — pour les actions simples (LOGIN, LOGOUT, EXPORT...). */
  @Transactional
  public void logSimple(String entityType, UUID entityId, String actionType, String description) {
    log(entityType, entityId, actionType, description, null);
  }

  private String toJson(Object o) {
    if (o == null) return null;
    try {
      return objectMapper.writeValueAsString(o);
    } catch (Exception e) {
      LOG.warnf("Audit trail JSON serialization failed for %s : %s",
          o.getClass().getSimpleName(), e.getMessage());
      return null;
    }
  }
}
