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

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

import dev.lions.unionflow.server.api.dto.kyc.request.CreateBeneficiaireEffectifRequest;
import dev.lions.unionflow.server.entity.BeneficiaireEffectif;
import dev.lions.unionflow.server.entity.KycDossier;
import dev.lions.unionflow.server.repository.BeneficiaireEffectifRepository;
import dev.lions.unionflow.server.repository.KycDossierRepository;
import dev.lions.unionflow.server.security.AuditTrailService;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
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 BeneficiaireEffectifServiceTest {

  @Mock BeneficiaireEffectifRepository repository;
  @Mock KycDossierRepository kycDossierRepository;
  @Mock AuditTrailService auditTrailService;

  private BeneficiaireEffectifService service;

  @BeforeEach
  void setUp() throws Exception {
    service = new BeneficiaireEffectifService();
    inject(service, "repository", repository);
    inject(service, "kycDossierRepository", kycDossierRepository);
    inject(service, "auditTrail", auditTrailService);
  }

  private void inject(Object t, String name, Object value) throws Exception {
    Field f = t.getClass().getDeclaredField(name);
    f.setAccessible(true);
    f.set(t, value);
  }

  private BeneficiaireEffectif ubo(BigDecimal pct, UUID kycId) {
    BeneficiaireEffectif b = new BeneficiaireEffectif();
    b.setId(UUID.randomUUID());
    b.setPourcentageCapital(pct);
    b.setActif(true);
    if (kycId != null) {
      KycDossier k = new KycDossier();
      k.setId(kycId);
      b.setKycDossier(k);
    }
    return b;
  }

  // ── verifierSeuilPourcentage ──────────────────────────────────────────────

  @Test
  @DisplayName("Pourcentage cumulé < 100 → OK")
  void seuil_OK() {
    UUID kyc = UUID.randomUUID();
    when(repository.findByKycDossier(kyc))
        .thenReturn(List.of(ubo(new BigDecimal("30"), kyc), ubo(new BigDecimal("40"), kyc)));
    service.verifierSeuilPourcentage(kyc, null, new BigDecimal("25"));
    // pas d'exception
  }

  @Test
  @DisplayName("Pourcentage cumulé == 100 → OK (limite incluse)")
  void seuil_LimiteOK() {
    UUID kyc = UUID.randomUUID();
    when(repository.findByKycDossier(kyc))
        .thenReturn(List.of(ubo(new BigDecimal("60"), kyc)));
    service.verifierSeuilPourcentage(kyc, null, new BigDecimal("40"));
  }

  @Test
  @DisplayName("Pourcentage cumulé > 100 → exception")
  void seuil_Depasse() {
    UUID kyc = UUID.randomUUID();
    when(repository.findByKycDossier(kyc))
        .thenReturn(List.of(ubo(new BigDecimal("60"), kyc), ubo(new BigDecimal("30"), kyc)));
    assertThatThrownBy(() ->
        service.verifierSeuilPourcentage(kyc, null, new BigDecimal("20")))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("dépasserait 100");
  }

  @Test
  @DisplayName("excludeId ignore l'UBO en cours de modification")
  void seuil_ExcludeId() {
    UUID kyc = UUID.randomUUID();
    UUID excludeId = UUID.randomUUID();
    BeneficiaireEffectif existant = ubo(new BigDecimal("60"), kyc);
    existant.setId(excludeId);
    when(repository.findByKycDossier(kyc))
        .thenReturn(List.of(existant, ubo(new BigDecimal("30"), kyc)));
    // 80 nouveau, excludeId enlève les 60 existants → cumul = 30 + 80 = 110 → fail
    assertThatThrownBy(() ->
        service.verifierSeuilPourcentage(kyc, excludeId, new BigDecimal("80")))
        .isInstanceOf(IllegalArgumentException.class);
    // mais 50 → cumul = 30 + 50 = 80 → OK
    service.verifierSeuilPourcentage(kyc, excludeId, new BigDecimal("50"));
  }

  @Test
  @DisplayName("Pourcentage null → no-op")
  void seuil_PourcentageNull() {
    service.verifierSeuilPourcentage(UUID.randomUUID(), null, null);
  }

  @Test
  @DisplayName("UBOs inactifs ignorés du cumul")
  void seuil_IgnoreInactifs() {
    UUID kyc = UUID.randomUUID();
    BeneficiaireEffectif inactif = ubo(new BigDecimal("80"), kyc);
    inactif.setActif(false);
    when(repository.findByKycDossier(kyc))
        .thenReturn(List.of(inactif));
    // 80 inactif ignoré → cumul = 30 → OK
    service.verifierSeuilPourcentage(kyc, null, new BigDecimal("30"));
  }

  // ── creer : validation cible ─────────────────────────────────────────────

  @Test
  @DisplayName("creer sans aucune cible → exception")
  void creer_AucuneCible() {
    CreateBeneficiaireEffectifRequest req = CreateBeneficiaireEffectifRequest.builder()
        .nom("Doe").prenoms("John")
        .dateNaissance(LocalDate.of(1980, 1, 1))
        .nationalite("CIV")
        .natureControle("DETENTION_CAPITAL")
        .build();
    assertThatThrownBy(() -> service.creer(req))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("identifiant cible");
  }

  // ── toResponse mapping ───────────────────────────────────────────────────

  @Test
  @DisplayName("toResponse mappe correctement les champs principaux")
  void toResponse_Mapping() {
    UUID id = UUID.randomUUID();
    BeneficiaireEffectif e = new BeneficiaireEffectif();
    e.setId(id);
    e.setNom("Dupont");
    e.setPrenoms("Jean");
    e.setNationalite("CIV");
    e.setEstPep(true);
    e.setNatureControle("DETENTION_CAPITAL");
    e.setActif(true);
    var r = service.toResponse(e);
    assertThat(r.id()).isEqualTo(id);
    assertThat(r.nom()).isEqualTo("Dupont");
    assertThat(r.prenoms()).isEqualTo("Jean");
    assertThat(r.estPep()).isTrue();
    assertThat(r.natureControle()).isEqualTo("DETENTION_CAPITAL");
  }

}
