package dev.lions.unionflow.server.service.audit;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.notification.request.CreateNotificationRequest;
import dev.lions.unionflow.server.security.AuditOperationLoggedEvent;
import dev.lions.unionflow.server.service.NotificationService;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class AuditNotificationServiceTest {

  @Mock NotificationService notificationService;
  private AuditNotificationService service;

  @BeforeEach
  void setUp() throws Exception {
    service = new AuditNotificationService();
    Field f = AuditNotificationService.class.getDeclaredField("notificationService");
    f.setAccessible(true);
    f.set(service, notificationService);
  }

  private AuditOperationLoggedEvent event(String action, Boolean sod) {
    return new AuditOperationLoggedEvent(
        UUID.randomUUID(), UUID.randomUUID(), "test@unionflow.dev",
        UUID.randomUUID(), action, "Cotisation", UUID.randomUUID(),
        "Test description", sod, LocalDateTime.now());
  }

  @Test
  @DisplayName("Event sensible (PAYMENT_CONFIRMED) → notification créée")
  void sensiblePaymentConfirmed() {
    when(notificationService.creerNotification(any())).thenReturn(null);
    service.onAuditOperation(event("PAYMENT_CONFIRMED", true));
    verify(notificationService).creerNotification(any(CreateNotificationRequest.class));
  }

  @Test
  @DisplayName("Event non-sensible (CREATE Membre) → pas de notification")
  void nonSensibleSkipped() {
    service.onAuditOperation(event("CREATE", true));
    verify(notificationService, never()).creerNotification(any());
  }

  @Test
  @DisplayName("Violation SoD → notification HAUTE priorité même sur action non-listée")
  void sodViolationAlwaysNotifies() {
    when(notificationService.creerNotification(any())).thenReturn(null);
    service.onAuditOperation(event("CREATE", false));
    verify(notificationService).creerNotification(any(CreateNotificationRequest.class));
  }

  @Test
  @DisplayName("Event null → ignoré silencieusement")
  void eventNullIgnored() {
    service.onAuditOperation(null);
    verify(notificationService, never()).creerNotification(any());
  }

  @Test
  @DisplayName("UserId null → pas de notification (pas de cible)")
  void userIdNullSkipped() {
    AuditOperationLoggedEvent e = new AuditOperationLoggedEvent(
        UUID.randomUUID(), null, "anon", UUID.randomUUID(),
        "DELETE", "X", UUID.randomUUID(), "test", true, LocalDateTime.now());
    service.onAuditOperation(e);
    verify(notificationService, never()).creerNotification(any());
  }

  // ── Helpers de mapping ────────────────────────────────────────────────

  @Test
  @DisplayName("sujetPourEvent — actions classiques")
  void sujetMapping() {
    assertThat(service.sujetPourEvent(event("PAYMENT_INITIATED", true)))
        .isEqualTo("Paiement initié");
    assertThat(service.sujetPourEvent(event("PAYMENT_CONFIRMED", true)))
        .isEqualTo("Paiement confirmé");
    assertThat(service.sujetPourEvent(event("PAYMENT_FAILED", true)))
        .startsWith("⚠");
    assertThat(service.sujetPourEvent(event("BUDGET_APPROVED", true)))
        .isEqualTo("Budget approuvé");
    assertThat(service.sujetPourEvent(event("AID_REQUEST_APPROVED", true)))
        .isEqualTo("Demande d'aide approuvée");
    assertThat(service.sujetPourEvent(event("EXPORT", true)))
        .isEqualTo("Export effectué");
    assertThat(service.sujetPourEvent(event("DELETE", true)))
        .startsWith("Suppression");
    assertThat(service.sujetPourEvent(event("VALIDATE", true)))
        .startsWith("Validation");
  }

  @Test
  @DisplayName("sujetPourEvent — violation SoD prioritaire sur actionType")
  void sujetSodPriority() {
    assertThat(service.sujetPourEvent(event("PAYMENT_CONFIRMED", false)))
        .startsWith("⚠ Violation SoD");
  }

  @Test
  @DisplayName("prioritePourEvent — règles")
  void prioriteMapping() {
    assertThat(service.prioritePourEvent(event("DELETE", true))).isEqualTo("HAUTE");
    assertThat(service.prioritePourEvent(event("PAYMENT_FAILED", true))).isEqualTo("HAUTE");
    assertThat(service.prioritePourEvent(event("PAYMENT_CONFIRMED", true))).isEqualTo("NORMALE");
    assertThat(service.prioritePourEvent(event("BUDGET_APPROVED", true))).isEqualTo("NORMALE");
    assertThat(service.prioritePourEvent(event("EXPORT", true))).isEqualTo("BASSE");
    assertThat(service.prioritePourEvent(event("CREATE", false))).isEqualTo("HAUTE"); // SoD
  }

  @Test
  @DisplayName("corpsPourEvent — contient les champs clés")
  void corpsBuilder() {
    String body = service.corpsPourEvent(event("PAYMENT_CONFIRMED", true));
    assertThat(body)
        .contains("Acteur :")
        .contains("test@unionflow.dev")
        .contains("Action :")
        .contains("PAYMENT_CONFIRMED")
        .contains("Entité :")
        .contains("Cotisation");
  }

  @Test
  @DisplayName("corpsPourEvent — note SoD si violation")
  void corpsSodNote() {
    String body = service.corpsPourEvent(event("PAYMENT_CONFIRMED", false));
    assertThat(body).contains("séparation des pouvoirs");
  }
}
