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

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

import dev.lions.unionflow.server.entity.AuditTrailOperation;
import dev.lions.unionflow.server.repository.AuditTrailOperationRepository;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.List;
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 AuditTrailQueryServiceTest {

  @Mock AuditTrailOperationRepository repository;

  private AuditTrailQueryService service;

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

  private AuditTrailOperation op(String entityType, String actionType, Boolean sodOk) {
    AuditTrailOperation o = new AuditTrailOperation();
    o.setId(UUID.randomUUID());
    o.setUserId(UUID.randomUUID());
    o.setEntityType(entityType);
    o.setActionType(actionType);
    o.setSodCheckPassed(sodOk);
    o.setOperationAt(LocalDateTime.now());
    return o;
  }

  @Test
  @DisplayName("rechercherParUtilisateur appelle le repo + mappe en DTO")
  void parUtilisateur() {
    UUID userId = UUID.randomUUID();
    LocalDateTime from = LocalDateTime.now().minusDays(1);
    LocalDateTime to = LocalDateTime.now();
    when(repository.findByUserBetween(userId, from, to))
        .thenReturn(List.of(op("Membre", "UPDATE", true), op("Cotisation", "CREATE", true)));

    var result = service.rechercherParUtilisateur(userId, from, to);
    assertThat(result).hasSize(2);
    assertThat(result).extracting("actionType").containsExactly("UPDATE", "CREATE");
  }

  @Test
  @DisplayName("violationsSod retourne la liste des violations")
  void sodViolations() {
    when(repository.findSoDViolations())
        .thenReturn(List.of(op("BudgetApproval", "VALIDATE", false)));
    var result = service.violationsSod();
    assertThat(result).hasSize(1);
    assertThat(result.get(0).sodCheckPassed()).isFalse();
  }

  @Test
  @DisplayName("toResponse mappe tous les champs")
  void toResponseMapping() {
    UUID id = UUID.randomUUID();
    UUID userId = UUID.randomUUID();
    UUID orgId = UUID.randomUUID();

    AuditTrailOperation e = AuditTrailOperation.builder()
        .id(id).userId(userId).userEmail("a@b.com").roleActif("TRESORIER")
        .organisationActiveId(orgId).actionType("PAYMENT_INITIATED").entityType("Paiement")
        .description("test").sodCheckPassed(true)
        .operationAt(LocalDateTime.of(2026, 4, 25, 12, 0))
        .build();
    var r = service.toResponse(e);
    assertThat(r.id()).isEqualTo(id);
    assertThat(r.userEmail()).isEqualTo("a@b.com");
    assertThat(r.actionType()).isEqualTo("PAYMENT_INITIATED");
    assertThat(r.organisationActiveId()).isEqualTo(orgId);
    assertThat(r.sodCheckPassed()).isTrue();
  }

  @Test
  @DisplayName("historiqueEntite filtre par type et id")
  void historique() {
    UUID id = UUID.randomUUID();
    when(repository.findByEntity("Membre", id))
        .thenReturn(List.of(op("Membre", "UPDATE", true)));
    var result = service.historiqueEntite("Membre", id);
    assertThat(result).hasSize(1);
    assertThat(result.get(0).entityType()).isEqualTo("Membre");
  }

  // ── Sprint 15 : Live Activity Feed (listerRecentes) ─────────────────────────

  @Test
  @DisplayName("listerRecentes scope=ALL appelle findRecent")
  void recentes_All() {
    when(repository.findRecent(50))
        .thenReturn(List.of(op("Membre", "CREATE", true), op("Cotisation", "UPDATE", true)));
    var result = service.listerRecentes("ALL", null, null, 50);
    assertThat(result).hasSize(2);
  }

  @Test
  @DisplayName("listerRecentes scope=null → ALL par défaut")
  void recentes_NullScopeDefaultsToAll() {
    when(repository.findRecent(20)).thenReturn(List.of(op("X", "CREATE", true)));
    var result = service.listerRecentes(null, null, null, 20);
    assertThat(result).hasSize(1);
  }

  @Test
  @DisplayName("listerRecentes scope=ORG appelle findRecentByOrganisation")
  void recentes_Org() {
    UUID orgId = UUID.randomUUID();
    when(repository.findRecentByOrganisation(orgId, 30))
        .thenReturn(List.of(op("Cotisation", "VALIDATE", true)));
    var result = service.listerRecentes("ORG", orgId, null, 30);
    assertThat(result).hasSize(1);
  }

  @Test
  @DisplayName("listerRecentes scope=ORG sans orgId → liste vide")
  void recentes_OrgWithoutId() {
    var result = service.listerRecentes("ORG", null, null, 30);
    assertThat(result).isEmpty();
  }

  @Test
  @DisplayName("listerRecentes scope=SELF appelle findRecentByUser")
  void recentes_Self() {
    UUID userId = UUID.randomUUID();
    when(repository.findRecentByUser(userId, 25))
        .thenReturn(List.of(op("Profil", "UPDATE", true)));
    var result = service.listerRecentes("SELF", null, userId, 25);
    assertThat(result).hasSize(1);
  }

  @Test
  @DisplayName("listerRecentes scope=SELF sans userId → liste vide")
  void recentes_SelfWithoutUser() {
    var result = service.listerRecentes("SELF", null, null, 25);
    assertThat(result).isEmpty();
  }

  @Test
  @DisplayName("listerRecentes scope insensible à la casse")
  void recentes_CaseInsensitive() {
    when(repository.findRecent(10)).thenReturn(List.of(op("X", "CREATE", true)));
    var result = service.listerRecentes("all", null, null, 10);
    assertThat(result).hasSize(1);
  }
}
