package dev.lions.unionflow.server.service;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import dev.lions.unionflow.server.api.dto.solidarite.aide.AideDTO;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import dev.lions.unionflow.server.entity.Aide;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.AideRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.security.KeycloakService;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;

/**
 * Tests unitaires pour AideService
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-15
 */
@QuarkusTest
@DisplayName("AideService - Tests unitaires")
class AideServiceTest {

  @Inject AideService aideService;

  @Mock AideRepository aideRepository;

  @Mock MembreRepository membreRepository;

  @Mock OrganisationRepository organisationRepository;

  @Mock KeycloakService keycloakService;

  private Membre membreTest;
  private Organisation organisationTest;
  private Aide aideTest;
  private AideDTO aideDTOTest;

  @BeforeEach
  void setUp() {
    // Membre de test
    membreTest = new Membre();
    membreTest.id = 1L;
    membreTest.setNumeroMembre("UF-2025-TEST001");
    membreTest.setNom("Dupont");
    membreTest.setPrenom("Jean");
    membreTest.setEmail("jean.dupont@test.com");
    membreTest.setActif(true);

    // Organisation de test
    organisationTest = new Organisation();
    organisationTest.id = 1L;
    organisationTest.setNom("Lions Club Test");
    organisationTest.setEmail("contact@lionstest.com");
    organisationTest.setActif(true);

    // Aide de test
    aideTest = new Aide();
    aideTest.id = 1L;
    aideTest.setNumeroReference("AIDE-2025-TEST01");
    aideTest.setTitre("Aide médicale urgente");
    aideTest.setDescription("Demande d'aide pour frais médicaux urgents");
    aideTest.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);
    aideTest.setMontantDemande(new BigDecimal("500000.00"));
    aideTest.setStatut(StatutAide.EN_ATTENTE);
    aideTest.setPriorite("URGENTE");
    aideTest.setMembreDemandeur(membreTest);
    aideTest.setOrganisation(organisationTest);
    aideTest.setActif(true);
    aideTest.setDateCreation(LocalDateTime.now());

    // DTO de test
    aideDTOTest = new AideDTO();
    aideDTOTest.setId(UUID.randomUUID());
    aideDTOTest.setNumeroReference("AIDE-2025-TEST01");
    aideDTOTest.setTitre("Aide médicale urgente");
    aideDTOTest.setDescription("Demande d'aide pour frais médicaux urgents");
    aideDTOTest.setTypeAide("MEDICALE");
    aideDTOTest.setMontantDemande(new BigDecimal("500000.00"));
    aideDTOTest.setStatut("EN_ATTENTE");
    aideDTOTest.setPriorite("URGENTE");
    aideDTOTest.setMembreDemandeurId(UUID.randomUUID());
    aideDTOTest.setAssociationId(UUID.randomUUID());
    aideDTOTest.setActif(true);
  }

  @Nested
  @DisplayName("Tests de création d'aide")
  class CreationAideTests {

    @Test
    @DisplayName("Création d'aide réussie")
    void testCreerAide_Success() {
      // Given
      when(membreRepository.findByIdOptional(anyLong())).thenReturn(Optional.of(membreTest));
      when(organisationRepository.findByIdOptional(anyLong()))
          .thenReturn(Optional.of(organisationTest));
      when(keycloakService.getCurrentUserEmail()).thenReturn("admin@test.com");

      ArgumentCaptor<Aide> aideCaptor = ArgumentCaptor.forClass(Aide.class);
      doNothing().when(aideRepository).persist(aideCaptor.capture());

      // When
      AideDTO result = aideService.creerAide(aideDTOTest);

      // Then
      assertThat(result).isNotNull();
      assertThat(result.getTitre()).isEqualTo(aideDTOTest.getTitre());
      assertThat(result.getDescription()).isEqualTo(aideDTOTest.getDescription());

      Aide aidePersistee = aideCaptor.getValue();
      assertThat(aidePersistee.getTitre()).isEqualTo(aideDTOTest.getTitre());
      assertThat(aidePersistee.getMembreDemandeur()).isEqualTo(membreTest);
      assertThat(aidePersistee.getOrganisation()).isEqualTo(organisationTest);
      assertThat(aidePersistee.getCreePar()).isEqualTo("admin@test.com");

      verify(aideRepository).persist(any(Aide.class));
    }

    @Test
    @DisplayName("Création d'aide - Membre non trouvé")
    void testCreerAide_MembreNonTrouve() {
      // Given
      when(membreRepository.findByIdOptional(anyLong())).thenReturn(Optional.empty());

      // When & Then
      assertThatThrownBy(() -> aideService.creerAide(aideDTOTest))
          .isInstanceOf(NotFoundException.class)
          .hasMessageContaining("Membre demandeur non trouvé");

      verify(aideRepository, never()).persist(any(Aide.class));
    }

    @Test
    @DisplayName("Création d'aide - Organisation non trouvée")
    void testCreerAide_OrganisationNonTrouvee() {
      // Given
      when(membreRepository.findByIdOptional(anyLong())).thenReturn(Optional.of(membreTest));
      when(organisationRepository.findByIdOptional(anyLong())).thenReturn(Optional.empty());

      // When & Then
      assertThatThrownBy(() -> aideService.creerAide(aideDTOTest))
          .isInstanceOf(NotFoundException.class)
          .hasMessageContaining("Organisation non trouvée");

      verify(aideRepository, never()).persist(any(Aide.class));
    }

    @Test
    @DisplayName("Création d'aide - Montant invalide")
    void testCreerAide_MontantInvalide() {
      // Given
      aideDTOTest.setMontantDemande(new BigDecimal("-100.00"));
      when(membreRepository.findByIdOptional(anyLong())).thenReturn(Optional.of(membreTest));
      when(organisationRepository.findByIdOptional(anyLong()))
          .thenReturn(Optional.of(organisationTest));

      // When & Then
      assertThatThrownBy(() -> aideService.creerAide(aideDTOTest))
          .isInstanceOf(IllegalArgumentException.class)
          .hasMessageContaining("Le montant demandé doit être positif");

      verify(aideRepository, never()).persist(any(Aide.class));
    }
  }

  @Nested
  @DisplayName("Tests de récupération d'aide")
  class RecuperationAideTests {

    @Test
    @DisplayName("Récupération d'aide par ID réussie")
    void testObtenirAideParId_Success() {
      // Given
      when(aideRepository.findByIdOptional(1L)).thenReturn(Optional.of(aideTest));
      when(keycloakService.getCurrentUserEmail()).thenReturn("autre@test.com");

      // When
      AideDTO result = aideService.obtenirAideParId(1L);

      // Then
      assertThat(result).isNotNull();
      assertThat(result.getTitre()).isEqualTo(aideTest.getTitre());
      assertThat(result.getDescription()).isEqualTo(aideTest.getDescription());
      assertThat(result.getStatut()).isEqualTo(aideTest.getStatut().name());
    }

    @Test
    @DisplayName("Récupération d'aide par ID - Non trouvée")
    void testObtenirAideParId_NonTrouvee() {
      // Given
      when(aideRepository.findByIdOptional(999L)).thenReturn(Optional.empty());

      // When & Then
      assertThatThrownBy(() -> aideService.obtenirAideParId(999L))
          .isInstanceOf(NotFoundException.class)
          .hasMessageContaining("Demande d'aide non trouvée");
    }

    @Test
    @DisplayName("Récupération d'aide par référence réussie")
    void testObtenirAideParReference_Success() {
      // Given
      String reference = "AIDE-2025-TEST01";
      when(aideRepository.findByNumeroReference(reference)).thenReturn(Optional.of(aideTest));
      when(keycloakService.getCurrentUserEmail()).thenReturn("autre@test.com");

      // When
      AideDTO result = aideService.obtenirAideParReference(reference);

      // Then
      assertThat(result).isNotNull();
      assertThat(result.getNumeroReference()).isEqualTo(reference);
    }
  }

  @Nested
  @DisplayName("Tests de mise à jour d'aide")
  class MiseAJourAideTests {

    @Test
    @DisplayName("Mise à jour d'aide réussie")
    void testMettreAJourAide_Success() {
      // Given
      when(aideRepository.findByIdOptional(1L)).thenReturn(Optional.of(aideTest));
      when(keycloakService.getCurrentUserEmail()).thenReturn("jean.dupont@test.com");
      when(keycloakService.hasRole("admin")).thenReturn(false);
      when(keycloakService.hasRole("gestionnaire_aide")).thenReturn(false);

      AideDTO aideMiseAJour = new AideDTO();
      aideMiseAJour.setTitre("Titre modifié");
      aideMiseAJour.setDescription("Description modifiée");
      aideMiseAJour.setMontantDemande(new BigDecimal("600000.00"));
      aideMiseAJour.setPriorite("HAUTE");

      // When
      AideDTO result = aideService.mettreAJourAide(1L, aideMiseAJour);

      // Then
      assertThat(result).isNotNull();
      assertThat(aideTest.getTitre()).isEqualTo("Titre modifié");
      assertThat(aideTest.getDescription()).isEqualTo("Description modifiée");
      assertThat(aideTest.getMontantDemande()).isEqualTo(new BigDecimal("600000.00"));
      assertThat(aideTest.getPriorite()).isEqualTo("HAUTE");
    }

    @Test
    @DisplayName("Mise à jour d'aide - Accès non autorisé")
    void testMettreAJourAide_AccesNonAutorise() {
      // Given
      when(aideRepository.findByIdOptional(1L)).thenReturn(Optional.of(aideTest));
      when(keycloakService.getCurrentUserEmail()).thenReturn("autre@test.com");
      when(keycloakService.hasRole("admin")).thenReturn(false);
      when(keycloakService.hasRole("gestionnaire_aide")).thenReturn(false);

      AideDTO aideMiseAJour = new AideDTO();
      aideMiseAJour.setTitre("Titre modifié");

      // When & Then
      assertThatThrownBy(() -> aideService.mettreAJourAide(1L, aideMiseAJour))
          .isInstanceOf(SecurityException.class)
          .hasMessageContaining("Vous n'avez pas les permissions");
    }
  }

  @Nested
  @DisplayName("Tests de conversion DTO/Entity")
  class ConversionTests {

    @Test
    @DisplayName("Conversion Entity vers DTO")
    void testConvertToDTO() {
      // When
      AideDTO result = aideService.convertToDTO(aideTest);

      // Then
      assertThat(result).isNotNull();
      assertThat(result.getTitre()).isEqualTo(aideTest.getTitre());
      assertThat(result.getDescription()).isEqualTo(aideTest.getDescription());
      assertThat(result.getMontantDemande()).isEqualTo(aideTest.getMontantDemande());
      assertThat(result.getStatut()).isEqualTo(aideTest.getStatut().name());
      assertThat(result.getTypeAide()).isEqualTo(aideTest.getTypeAide().name());
    }

    @Test
    @DisplayName("Conversion DTO vers Entity")
    void testConvertFromDTO() {
      // When
      Aide result = aideService.convertFromDTO(aideDTOTest);

      // Then
      assertThat(result).isNotNull();
      assertThat(result.getTitre()).isEqualTo(aideDTOTest.getTitre());
      assertThat(result.getDescription()).isEqualTo(aideDTOTest.getDescription());
      assertThat(result.getMontantDemande()).isEqualTo(aideDTOTest.getMontantDemande());
      assertThat(result.getStatut()).isEqualTo(StatutAide.EN_ATTENTE);
      assertThat(result.getTypeAide()).isEqualTo(TypeAide.AIDE_FRAIS_MEDICAUX);
    }

    @Test
    @DisplayName("Conversion DTO null")
    void testConvertFromDTO_Null() {
      // When
      Aide result = aideService.convertFromDTO(null);

      // Then
      assertThat(result).isNull();
    }
  }
}
