package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.test.junit.QuarkusTest;
import org.mockito.Mock;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * Tests unitaires pour OrganisationService
 * 
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-15
 */
@QuarkusTest
class OrganisationServiceTest {

    @Inject
    OrganisationService organisationService;

    @Mock
    OrganisationRepository organisationRepository;

    private Organisation organisationTest;

    @BeforeEach
    void setUp() {
        organisationTest = Organisation.builder()
                .nom("Lions Club Test")
                .nomCourt("LC Test")
                .email("test@lionsclub.org")
                .typeOrganisation("LIONS_CLUB")
                .statut("ACTIVE")
                .description("Organisation de test")
                .telephone("+225 01 02 03 04 05")
                .adresse("123 Rue de Test")
                .ville("Abidjan")
                .region("Lagunes")
                .pays("Côte d'Ivoire")
                .nombreMembres(25)
                .actif(true)
                .dateCreation(LocalDateTime.now())
                .version(0L)
                .build();
        organisationTest.id = 1L;
    }

    @Test
    void testCreerOrganisation_Success() {
        // Given
        Organisation organisationToCreate = Organisation.builder()
                .nom("Lions Club Test New")
                .email("testnew@lionsclub.org")
                .typeOrganisation("LIONS_CLUB")
                .build();

        when(organisationRepository.findByEmail("testnew@lionsclub.org")).thenReturn(Optional.empty());
        when(organisationRepository.findByNom("Lions Club Test New")).thenReturn(Optional.empty());

        // When
        Organisation result = organisationService.creerOrganisation(organisationToCreate);

        // Then
        assertNotNull(result);
        assertEquals("Lions Club Test New", result.getNom());
        assertEquals("ACTIVE", result.getStatut());
        verify(organisationRepository).findByEmail("testnew@lionsclub.org");
        verify(organisationRepository).findByNom("Lions Club Test New");
    }

    @Test
    void testCreerOrganisation_EmailDejaExistant() {
        // Given
        when(organisationRepository.findByEmail(anyString())).thenReturn(Optional.of(organisationTest));

        // When & Then
        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, 
            () -> organisationService.creerOrganisation(organisationTest));
        
        assertEquals("Une organisation avec cet email existe déjà", exception.getMessage());
        verify(organisationRepository).findByEmail("test@lionsclub.org");
        verify(organisationRepository, never()).findByNom(anyString());
    }

    @Test
    void testCreerOrganisation_NomDejaExistant() {
        // Given
        when(organisationRepository.findByEmail(anyString())).thenReturn(Optional.empty());
        when(organisationRepository.findByNom(anyString())).thenReturn(Optional.of(organisationTest));

        // When & Then
        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, 
            () -> organisationService.creerOrganisation(organisationTest));
        
        assertEquals("Une organisation avec ce nom existe déjà", exception.getMessage());
        verify(organisationRepository).findByEmail("test@lionsclub.org");
        verify(organisationRepository).findByNom("Lions Club Test");
    }

    @Test
    void testMettreAJourOrganisation_Success() {
        // Given
        Organisation organisationMiseAJour = Organisation.builder()
                .nom("Lions Club Test Modifié")
                .email("test@lionsclub.org")
                .description("Description modifiée")
                .telephone("+225 01 02 03 04 06")
                .build();

        when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest));
        when(organisationRepository.findByNom("Lions Club Test Modifié")).thenReturn(Optional.empty());

        // When
        Organisation result = organisationService.mettreAJourOrganisation(1L, organisationMiseAJour, "testUser");

        // Then
        assertNotNull(result);
        assertEquals("Lions Club Test Modifié", result.getNom());
        assertEquals("Description modifiée", result.getDescription());
        assertEquals("+225 01 02 03 04 06", result.getTelephone());
        assertEquals("testUser", result.getModifiePar());
        assertNotNull(result.getDateModification());
        assertEquals(1L, result.getVersion());
    }

    @Test
    void testMettreAJourOrganisation_OrganisationNonTrouvee() {
        // Given
        when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.empty());

        // When & Then
        NotFoundException exception = assertThrows(NotFoundException.class, 
            () -> organisationService.mettreAJourOrganisation(1L, organisationTest, "testUser"));
        
        assertEquals("Organisation non trouvée avec l'ID: 1", exception.getMessage());
    }

    @Test
    void testSupprimerOrganisation_Success() {
        // Given
        organisationTest.setNombreMembres(0);
        when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest));

        // When
        organisationService.supprimerOrganisation(1L, "testUser");

        // Then
        assertFalse(organisationTest.getActif());
        assertEquals("DISSOUTE", organisationTest.getStatut());
        assertEquals("testUser", organisationTest.getModifiePar());
        assertNotNull(organisationTest.getDateModification());
    }

    @Test
    void testSupprimerOrganisation_AvecMembresActifs() {
        // Given
        organisationTest.setNombreMembres(5);
        when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest));

        // When & Then
        IllegalStateException exception = assertThrows(IllegalStateException.class, 
            () -> organisationService.supprimerOrganisation(1L, "testUser"));
        
        assertEquals("Impossible de supprimer une organisation avec des membres actifs", exception.getMessage());
    }

    @Test
    void testTrouverParId_Success() {
        // Given
        when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest));

        // When
        Optional<Organisation> result = organisationService.trouverParId(1L);

        // Then
        assertTrue(result.isPresent());
        assertEquals("Lions Club Test", result.get().getNom());
        verify(organisationRepository).findByIdOptional(1L);
    }

    @Test
    void testTrouverParId_NonTrouve() {
        // Given
        when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.empty());

        // When
        Optional<Organisation> result = organisationService.trouverParId(1L);

        // Then
        assertFalse(result.isPresent());
        verify(organisationRepository).findByIdOptional(1L);
    }

    @Test
    void testTrouverParEmail_Success() {
        // Given
        when(organisationRepository.findByEmail("test@lionsclub.org")).thenReturn(Optional.of(organisationTest));

        // When
        Optional<Organisation> result = organisationService.trouverParEmail("test@lionsclub.org");

        // Then
        assertTrue(result.isPresent());
        assertEquals("Lions Club Test", result.get().getNom());
        verify(organisationRepository).findByEmail("test@lionsclub.org");
    }

    @Test
    void testListerOrganisationsActives() {
        // Given
        List<Organisation> organisations = Arrays.asList(organisationTest);
        when(organisationRepository.findAllActives()).thenReturn(organisations);

        // When
        List<Organisation> result = organisationService.listerOrganisationsActives();

        // Then
        assertNotNull(result);
        assertEquals(1, result.size());
        assertEquals("Lions Club Test", result.get(0).getNom());
        verify(organisationRepository).findAllActives();
    }

    @Test
    void testActiverOrganisation_Success() {
        // Given
        organisationTest.setStatut("SUSPENDUE");
        organisationTest.setActif(false);
        when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest));

        // When
        Organisation result = organisationService.activerOrganisation(1L, "testUser");

        // Then
        assertNotNull(result);
        assertEquals("ACTIVE", result.getStatut());
        assertTrue(result.getActif());
        assertEquals("testUser", result.getModifiePar());
        assertNotNull(result.getDateModification());
    }

    @Test
    void testSuspendreOrganisation_Success() {
        // Given
        when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest));

        // When
        Organisation result = organisationService.suspendreOrganisation(1L, "testUser");

        // Then
        assertNotNull(result);
        assertEquals("SUSPENDUE", result.getStatut());
        assertFalse(result.getAccepteNouveauxMembres());
        assertEquals("testUser", result.getModifiePar());
        assertNotNull(result.getDateModification());
    }

    @Test
    void testObtenirStatistiques() {
        // Given
        when(organisationRepository.count()).thenReturn(100L);
        when(organisationRepository.countActives()).thenReturn(85L);
        when(organisationRepository.countNouvellesOrganisations(any(LocalDate.class))).thenReturn(5L);

        // When
        Map<String, Object> result = organisationService.obtenirStatistiques();

        // Then
        assertNotNull(result);
        assertEquals(100L, result.get("totalOrganisations"));
        assertEquals(85L, result.get("organisationsActives"));
        assertEquals(15L, result.get("organisationsInactives"));
        assertEquals(5L, result.get("nouvellesOrganisations30Jours"));
        assertEquals(85.0, result.get("tauxActivite"));
        assertNotNull(result.get("timestamp"));
    }

    @Test
    void testConvertToDTO() {
        // When
        var dto = organisationService.convertToDTO(organisationTest);

        // Then
        assertNotNull(dto);
        assertEquals("Lions Club Test", dto.getNom());
        assertEquals("LC Test", dto.getNomCourt());
        assertEquals("test@lionsclub.org", dto.getEmail());
        assertEquals("Organisation de test", dto.getDescription());
        assertEquals("+225 01 02 03 04 05", dto.getTelephone());
        assertEquals("Abidjan", dto.getVille());
        assertEquals(25, dto.getNombreMembres());
        assertTrue(dto.getActif());
    }

    @Test
    void testConvertToDTO_Null() {
        // When
        var dto = organisationService.convertToDTO(null);

        // Then
        assertNull(dto);
    }

    @Test
    void testConvertFromDTO() {
        // Given
        var dto = organisationService.convertToDTO(organisationTest);

        // When
        Organisation result = organisationService.convertFromDTO(dto);

        // Then
        assertNotNull(result);
        assertEquals("Lions Club Test", result.getNom());
        assertEquals("LC Test", result.getNomCourt());
        assertEquals("test@lionsclub.org", result.getEmail());
        assertEquals("Organisation de test", result.getDescription());
        assertEquals("+225 01 02 03 04 05", result.getTelephone());
        assertEquals("Abidjan", result.getVille());
    }

    @Test
    void testConvertFromDTO_Null() {
        // When
        Organisation result = organisationService.convertFromDTO(null);

        // Then
        assertNull(result);
    }
}
