package dev.lions.unionflow.server.resource.mutuelle.parts;

import dev.lions.unionflow.server.api.dto.mutuelle.parts.ComptePartsSocialesRequest;
import dev.lions.unionflow.server.api.dto.mutuelle.parts.ComptePartsSocialesResponse;
import dev.lions.unionflow.server.api.dto.mutuelle.parts.TransactionPartsSocialesRequest;
import dev.lions.unionflow.server.api.dto.mutuelle.parts.TransactionPartsSocialesResponse;
import dev.lions.unionflow.server.service.mutuelle.parts.ComptePartsSocialesService;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
@DisplayName("Tests ComptePartsSocialesResource")
class ComptePartsSocialesResourceTest {

    @Mock
    ComptePartsSocialesService service;

    @InjectMocks
    ComptePartsSocialesResource resource;

    private ComptePartsSocialesResponse buildCompteResponse() {
        return mock(ComptePartsSocialesResponse.class);
    }

    private TransactionPartsSocialesResponse buildTransactionResponse() {
        return mock(TransactionPartsSocialesResponse.class);
    }

    // ── POST / — ouvrirCompte ─────────────────────────────────────────────────

    @Nested
    @DisplayName("POST / — ouvrirCompte")
    class OuvrirCompte {

        @Test
        @DisplayName("retourne 201 avec la réponse du service après ouverture réussie")
        void ouvrirCompte_success_returns201() {
            ComptePartsSocialesRequest req = mock(ComptePartsSocialesRequest.class);
            ComptePartsSocialesResponse resp = buildCompteResponse();
            when(service.ouvrirCompte(req)).thenReturn(resp);

            Response r = resource.ouvrirCompte(req);

            assertThat(r.getStatus()).isEqualTo(201);
            assertThat(r.getEntity()).isEqualTo(resp);
        }

        @Test
        @DisplayName("appelle service.ouvrirCompte avec la requête fournie")
        void ouvrirCompte_passesRequestToService() {
            ComptePartsSocialesRequest req = mock(ComptePartsSocialesRequest.class);
            when(service.ouvrirCompte(req)).thenReturn(buildCompteResponse());

            resource.ouvrirCompte(req);

            verify(service, times(1)).ouvrirCompte(req);
        }

        @Test
        @DisplayName("propage IllegalArgumentException si membre introuvable")
        void ouvrirCompte_membreInexistant_propagates() {
            ComptePartsSocialesRequest req = mock(ComptePartsSocialesRequest.class);
            when(service.ouvrirCompte(req))
                    .thenThrow(new IllegalArgumentException("Membre introuvable"));

            assertThatThrownBy(() -> resource.ouvrirCompte(req))
                    .isInstanceOf(IllegalArgumentException.class)
                    .hasMessageContaining("Membre introuvable");
        }

        @Test
        @DisplayName("propage RuntimeException si compte déjà ouvert pour ce membre")
        void ouvrirCompte_compteDejaExistant_propagates() {
            ComptePartsSocialesRequest req = mock(ComptePartsSocialesRequest.class);
            when(service.ouvrirCompte(req))
                    .thenThrow(new RuntimeException("Un compte parts existe déjà pour ce membre"));

            assertThatThrownBy(() -> resource.ouvrirCompte(req))
                    .isInstanceOf(RuntimeException.class)
                    .hasMessageContaining("compte parts");
        }

        @Test
        @DisplayName("retourne le statut CREATED (201) et non OK (200)")
        void ouvrirCompte_statusIsCreated_notOk() {
            ComptePartsSocialesRequest req = mock(ComptePartsSocialesRequest.class);
            when(service.ouvrirCompte(req)).thenReturn(buildCompteResponse());

            Response r = resource.ouvrirCompte(req);

            assertThat(r.getStatus()).isEqualTo(Response.Status.CREATED.getStatusCode());
            assertThat(r.getStatus()).isNotEqualTo(200);
        }
    }

    // ── POST /transactions — enregistrerTransaction ───────────────────────────

    @Nested
    @DisplayName("POST /transactions — enregistrerTransaction")
    class EnregistrerTransaction {

        @Test
        @DisplayName("retourne 201 avec la réponse du service après enregistrement")
        void enregistrerTransaction_success_returns201() {
            TransactionPartsSocialesRequest req = mock(TransactionPartsSocialesRequest.class);
            TransactionPartsSocialesResponse resp = buildTransactionResponse();
            when(service.enregistrerSouscription(req)).thenReturn(resp);

            Response r = resource.enregistrerTransaction(req);

            assertThat(r.getStatus()).isEqualTo(201);
            assertThat(r.getEntity()).isEqualTo(resp);
        }

        @Test
        @DisplayName("appelle service.enregistrerSouscription avec la requête fournie")
        void enregistrerTransaction_passesRequestToService() {
            TransactionPartsSocialesRequest req = mock(TransactionPartsSocialesRequest.class);
            when(service.enregistrerSouscription(req)).thenReturn(buildTransactionResponse());

            resource.enregistrerTransaction(req);

            verify(service, times(1)).enregistrerSouscription(req);
        }

        @Test
        @DisplayName("propage IllegalArgumentException si montant invalide")
        void enregistrerTransaction_montantInvalide_propagates() {
            TransactionPartsSocialesRequest req = mock(TransactionPartsSocialesRequest.class);
            when(service.enregistrerSouscription(req))
                    .thenThrow(new IllegalArgumentException("Le montant doit être positif"));

            assertThatThrownBy(() -> resource.enregistrerTransaction(req))
                    .isInstanceOf(IllegalArgumentException.class)
                    .hasMessageContaining("montant");
        }

        @Test
        @DisplayName("propage RuntimeException si compte introuvable")
        void enregistrerTransaction_compteInexistant_propagates() {
            TransactionPartsSocialesRequest req = mock(TransactionPartsSocialesRequest.class);
            when(service.enregistrerSouscription(req))
                    .thenThrow(new RuntimeException("Compte parts introuvable"));

            assertThatThrownBy(() -> resource.enregistrerTransaction(req))
                    .isInstanceOf(RuntimeException.class);
        }

        @Test
        @DisplayName("retourne le statut CREATED (201) et non OK (200)")
        void enregistrerTransaction_statusIsCreated() {
            TransactionPartsSocialesRequest req = mock(TransactionPartsSocialesRequest.class);
            when(service.enregistrerSouscription(any())).thenReturn(buildTransactionResponse());

            Response r = resource.enregistrerTransaction(req);

            assertThat(r.getStatus()).isEqualTo(Response.Status.CREATED.getStatusCode());
        }
    }

    // ── GET /{id} — getById ───────────────────────────────────────────────────

    @Nested
    @DisplayName("GET /{id} — getById")
    class GetById {

        @Test
        @DisplayName("retourne 200 avec le compte trouvé")
        void getById_success_returns200() {
            UUID id = UUID.randomUUID();
            ComptePartsSocialesResponse resp = buildCompteResponse();
            when(service.getById(id)).thenReturn(resp);

            Response r = resource.getById(id);

            assertThat(r.getStatus()).isEqualTo(200);
            assertThat(r.getEntity()).isEqualTo(resp);
        }

        @Test
        @DisplayName("appelle service.getById avec l'UUID exact")
        void getById_passesCorrectUUID() {
            UUID id = UUID.fromString("11111111-2222-3333-4444-555555555555");
            when(service.getById(id)).thenReturn(buildCompteResponse());

            resource.getById(id);

            verify(service).getById(eq(id));
        }

        @Test
        @DisplayName("propage RuntimeException si compte introuvable")
        void getById_compteInconnu_propagates() {
            UUID id = UUID.randomUUID();
            when(service.getById(id))
                    .thenThrow(new RuntimeException("Compte introuvable: " + id));

            assertThatThrownBy(() -> resource.getById(id))
                    .isInstanceOf(RuntimeException.class);
        }

        @Test
        @DisplayName("retourne 200 même si entité est null (cas service)")
        void getById_null_returns200() {
            UUID id = UUID.randomUUID();
            when(service.getById(id)).thenReturn(null);

            Response r = resource.getById(id);

            assertThat(r.getStatus()).isEqualTo(200);
            assertThat(r.getEntity()).isNull();
        }
    }

    // ── GET /membre/{membreId} — getByMembre ─────────────────────────────────

    @Nested
    @DisplayName("GET /membre/{membreId} — getByMembre")
    class GetByMembre {

        @Test
        @DisplayName("retourne 200 avec la liste des comptes du membre")
        void getByMembre_success_returns200() {
            UUID membreId = UUID.randomUUID();
            List<ComptePartsSocialesResponse> list = List.of(buildCompteResponse(), buildCompteResponse());
            when(service.getByMembre(membreId)).thenReturn(list);

            Response r = resource.getByMembre(membreId);

            assertThat(r.getStatus()).isEqualTo(200);
            assertThat(r.getEntity()).isEqualTo(list);
        }

        @Test
        @DisplayName("retourne 200 avec liste vide quand le membre n'a aucun compte")
        void getByMembre_aucunCompte_returns200() {
            UUID membreId = UUID.randomUUID();
            when(service.getByMembre(membreId)).thenReturn(List.of());

            Response r = resource.getByMembre(membreId);

            assertThat(r.getStatus()).isEqualTo(200);
            @SuppressWarnings("unchecked")
            List<ComptePartsSocialesResponse> body = (List<ComptePartsSocialesResponse>) r.getEntity();
            assertThat(body).isEmpty();
        }

        @Test
        @DisplayName("appelle service.getByMembre avec l'UUID exact")
        void getByMembre_passesCorrectUUID() {
            UUID membreId = UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
            when(service.getByMembre(membreId)).thenReturn(List.of());

            resource.getByMembre(membreId);

            verify(service).getByMembre(eq(membreId));
        }

        @Test
        @DisplayName("propage RuntimeException levée par le service")
        void getByMembre_serviceException_propagates() {
            UUID membreId = UUID.randomUUID();
            when(service.getByMembre(membreId))
                    .thenThrow(new RuntimeException("Membre introuvable"));

            assertThatThrownBy(() -> resource.getByMembre(membreId))
                    .isInstanceOf(RuntimeException.class);
        }

        @Test
        @DisplayName("retourne 200 avec liste de plusieurs comptes")
        void getByMembre_plusieursComptes_returns200() {
            UUID membreId = UUID.randomUUID();
            List<ComptePartsSocialesResponse> list = List.of(
                    buildCompteResponse(), buildCompteResponse(), buildCompteResponse()
            );
            when(service.getByMembre(membreId)).thenReturn(list);

            Response r = resource.getByMembre(membreId);

            assertThat(r.getStatus()).isEqualTo(200);
            @SuppressWarnings("unchecked")
            List<ComptePartsSocialesResponse> body = (List<ComptePartsSocialesResponse>) r.getEntity();
            assertThat(body).hasSize(3);
        }
    }

    // ── GET /organisation/{orgId} — getByOrganisation ────────────────────────

    @Nested
    @DisplayName("GET /organisation/{orgId} — getByOrganisation")
    class GetByOrganisation {

        @Test
        @DisplayName("retourne 200 avec la liste des comptes de l'organisation")
        void getByOrganisation_success_returns200() {
            UUID orgId = UUID.randomUUID();
            List<ComptePartsSocialesResponse> list = List.of(buildCompteResponse());
            when(service.getByOrganisation(orgId)).thenReturn(list);

            Response r = resource.getByOrganisation(orgId);

            assertThat(r.getStatus()).isEqualTo(200);
            assertThat(r.getEntity()).isEqualTo(list);
        }

        @Test
        @DisplayName("retourne 200 avec liste vide si organisation sans comptes")
        void getByOrganisation_aucunCompte_returns200() {
            UUID orgId = UUID.randomUUID();
            when(service.getByOrganisation(orgId)).thenReturn(List.of());

            Response r = resource.getByOrganisation(orgId);

            assertThat(r.getStatus()).isEqualTo(200);
        }

        @Test
        @DisplayName("appelle service.getByOrganisation avec l'UUID exact")
        void getByOrganisation_passesCorrectUUID() {
            UUID orgId = UUID.fromString("12345678-abcd-efab-cdef-123456789abc");
            when(service.getByOrganisation(orgId)).thenReturn(List.of());

            resource.getByOrganisation(orgId);

            verify(service).getByOrganisation(eq(orgId));
        }

        @Test
        @DisplayName("propage RuntimeException levée par le service")
        void getByOrganisation_serviceException_propagates() {
            UUID orgId = UUID.randomUUID();
            when(service.getByOrganisation(orgId))
                    .thenThrow(new RuntimeException("Organisation introuvable"));

            assertThatThrownBy(() -> resource.getByOrganisation(orgId))
                    .isInstanceOf(RuntimeException.class);
        }
    }

    // ── GET /{id}/transactions — getTransactions ──────────────────────────────

    @Nested
    @DisplayName("GET /{id}/transactions — getTransactions")
    class GetTransactions {

        @Test
        @DisplayName("retourne 200 avec la liste des transactions")
        void getTransactions_success_returns200() {
            UUID id = UUID.randomUUID();
            List<TransactionPartsSocialesResponse> list = List.of(
                    buildTransactionResponse(), buildTransactionResponse()
            );
            when(service.getTransactions(id)).thenReturn(list);

            Response r = resource.getTransactions(id);

            assertThat(r.getStatus()).isEqualTo(200);
            assertThat(r.getEntity()).isEqualTo(list);
        }

        @Test
        @DisplayName("retourne 200 avec liste vide si aucune transaction")
        void getTransactions_aucuneTransaction_returns200() {
            UUID id = UUID.randomUUID();
            when(service.getTransactions(id)).thenReturn(List.of());

            Response r = resource.getTransactions(id);

            assertThat(r.getStatus()).isEqualTo(200);
            @SuppressWarnings("unchecked")
            List<TransactionPartsSocialesResponse> body =
                    (List<TransactionPartsSocialesResponse>) r.getEntity();
            assertThat(body).isEmpty();
        }

        @Test
        @DisplayName("appelle service.getTransactions avec l'UUID exact du compte")
        void getTransactions_passesCorrectUUID() {
            UUID id = UUID.fromString("99999999-8888-7777-6666-555555555555");
            when(service.getTransactions(id)).thenReturn(List.of());

            resource.getTransactions(id);

            verify(service).getTransactions(eq(id));
        }

        @Test
        @DisplayName("propage RuntimeException si compte introuvable")
        void getTransactions_compteInconnu_propagates() {
            UUID id = UUID.randomUUID();
            when(service.getTransactions(id))
                    .thenThrow(new RuntimeException("Compte introuvable: " + id));

            assertThatThrownBy(() -> resource.getTransactions(id))
                    .isInstanceOf(RuntimeException.class);
        }

        @Test
        @DisplayName("retourne 200 avec plusieurs transactions")
        void getTransactions_plusieursTransactions_returns200() {
            UUID id = UUID.randomUUID();
            List<TransactionPartsSocialesResponse> list = List.of(
                    buildTransactionResponse(),
                    buildTransactionResponse(),
                    buildTransactionResponse(),
                    buildTransactionResponse()
            );
            when(service.getTransactions(id)).thenReturn(list);

            Response r = resource.getTransactions(id);

            @SuppressWarnings("unchecked")
            List<TransactionPartsSocialesResponse> body =
                    (List<TransactionPartsSocialesResponse>) r.getEntity();
            assertThat(body).hasSize(4);
        }
    }
}
