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

import dev.lions.unionflow.server.service.mutuelle.ReleveComptePdfService;
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.UUID;

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

@ExtendWith(MockitoExtension.class)
@DisplayName("Tests ReleveCompteResource")
class ReleveCompteResourceTest {

    @Mock
    ReleveComptePdfService releveService;

    @InjectMocks
    ReleveCompteResource resource;

    private static final byte[] PDF_BYTES = new byte[]{0x25, 0x50, 0x44, 0x46}; // %PDF

    // ── GET /epargne/{compteId} ───────────────────────────────────────────────

    @Nested
    @DisplayName("GET /epargne/{compteId}")
    class ReleveEpargne {

        @Test
        @DisplayName("retourne 200 avec bytes PDF et headers Content-Disposition")
        void releveEpargne_success_returns200() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveEpargne(eq(compteId), isNull(), isNull()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveEpargne(compteId, null, null);

            assertThat(r.getStatus()).isEqualTo(200);
            assertThat(r.getEntity()).isEqualTo(PDF_BYTES);
            assertThat(r.getHeaderString("Content-Disposition"))
                    .contains("attachment")
                    .contains("releve-epargne-" + compteId);
        }

        @Test
        @DisplayName("transmet les dates parsées au service quand les deux sont fournies")
        void releveEpargne_avecDates_transmetsAuService() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveEpargne(eq(compteId), any(), any()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveEpargne(compteId, "2026-01-01", "2026-03-31");

            assertThat(r.getStatus()).isEqualTo(200);
            verify(releveService).genererReleveEpargne(
                    eq(compteId),
                    eq(java.time.LocalDate.of(2026, 1, 1)),
                    eq(java.time.LocalDate.of(2026, 3, 31))
            );
        }

        @Test
        @DisplayName("transmet dateDebut seule, dateFin null")
        void releveEpargne_seulementDebut_datFinNull() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveEpargne(eq(compteId), any(), isNull()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveEpargne(compteId, "2026-01-01", null);

            assertThat(r.getStatus()).isEqualTo(200);
            verify(releveService).genererReleveEpargne(
                    eq(compteId),
                    eq(java.time.LocalDate.of(2026, 1, 1)),
                    isNull()
            );
        }

        @Test
        @DisplayName("transmet dateFin seule, dateDebut null")
        void releveEpargne_seulementFin_dateDebutNull() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveEpargne(eq(compteId), isNull(), any()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveEpargne(compteId, null, "2026-03-31");

            assertThat(r.getStatus()).isEqualTo(200);
            verify(releveService).genererReleveEpargne(
                    eq(compteId),
                    isNull(),
                    eq(java.time.LocalDate.of(2026, 3, 31))
            );
        }

        @Test
        @DisplayName("string vide pour dateDebut est traité comme null")
        void releveEpargne_dateDebutVide_traiteeCommeNull() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveEpargne(eq(compteId), isNull(), isNull()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveEpargne(compteId, "", "");

            assertThat(r.getStatus()).isEqualTo(200);
            verify(releveService).genererReleveEpargne(eq(compteId), isNull(), isNull());
        }

        @Test
        @DisplayName("string blanke pour dateDebut est traitée comme null")
        void releveEpargne_dateBlanke_traiteeCommeNull() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveEpargne(eq(compteId), isNull(), isNull()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveEpargne(compteId, "   ", "   ");

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

        @Test
        @DisplayName("lève IllegalArgumentException si format de date invalide")
        void releveEpargne_dateFormatInvalide_throwsIllegalArgument() {
            UUID compteId = UUID.randomUUID();

            assertThatThrownBy(() -> resource.releveEpargne(compteId, "01-01-2026", null))
                    .isInstanceOf(IllegalArgumentException.class)
                    .hasMessageContaining("Format de date invalide")
                    .hasMessageContaining("YYYY-MM-DD");
        }

        @Test
        @DisplayName("lève IllegalArgumentException si dateFin invalide")
        void releveEpargne_dateFinInvalide_throwsIllegalArgument() {
            UUID compteId = UUID.randomUUID();

            assertThatThrownBy(() -> resource.releveEpargne(compteId, null, "2026/03/31"))
                    .isInstanceOf(IllegalArgumentException.class)
                    .hasMessageContaining("2026/03/31");
        }

        @Test
        @DisplayName("retourne 200 avec PDF vide (0 octets)")
        void releveEpargne_pdfVide_returns200() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveEpargne(any(), any(), any())).thenReturn(new byte[0]);

            Response r = resource.releveEpargne(compteId, null, null);

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

        @Test
        @DisplayName("propage RuntimeException levée par le service PDF")
        void releveEpargne_serviceException_propagates() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveEpargne(any(), any(), any()))
                    .thenThrow(new RuntimeException("Compte épargne introuvable"));

            assertThatThrownBy(() -> resource.releveEpargne(compteId, null, null))
                    .isInstanceOf(RuntimeException.class)
                    .hasMessageContaining("Compte épargne introuvable");
        }

        @Test
        @DisplayName("header Content-Disposition contient l'UUID du compte")
        void releveEpargne_contentDispositionContientUUID() {
            UUID compteId = UUID.fromString("12345678-1234-1234-1234-123456789012");
            when(releveService.genererReleveEpargne(any(), any(), any())).thenReturn(PDF_BYTES);

            Response r = resource.releveEpargne(compteId, null, null);

            assertThat(r.getHeaderString("Content-Disposition"))
                    .contains("12345678-1234-1234-1234-123456789012");
        }
    }

    // ── GET /parts-sociales/{compteId} ────────────────────────────────────────

    @Nested
    @DisplayName("GET /parts-sociales/{compteId}")
    class ReleveParts {

        @Test
        @DisplayName("retourne 200 avec bytes PDF et headers Content-Disposition")
        void releveParts_success_returns200() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveParts(eq(compteId), isNull(), isNull()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveParts(compteId, null, null);

            assertThat(r.getStatus()).isEqualTo(200);
            assertThat(r.getEntity()).isEqualTo(PDF_BYTES);
            assertThat(r.getHeaderString("Content-Disposition"))
                    .contains("attachment")
                    .contains("releve-parts-" + compteId);
        }

        @Test
        @DisplayName("transmet les dates parsées au service quand les deux sont fournies")
        void releveParts_avecDates_transmetsAuService() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveParts(eq(compteId), any(), any()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveParts(compteId, "2026-01-01", "2026-12-31");

            assertThat(r.getStatus()).isEqualTo(200);
            verify(releveService).genererReleveParts(
                    eq(compteId),
                    eq(java.time.LocalDate.of(2026, 1, 1)),
                    eq(java.time.LocalDate.of(2026, 12, 31))
            );
        }

        @Test
        @DisplayName("dateDebut seule — dateFin null")
        void releveParts_seulementDebut() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveParts(eq(compteId), any(), isNull()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveParts(compteId, "2025-06-01", null);

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

        @Test
        @DisplayName("dateFin seule — dateDebut null")
        void releveParts_seulementFin() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveParts(eq(compteId), isNull(), any()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveParts(compteId, null, "2026-06-30");

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

        @Test
        @DisplayName("string vide pour les dates est traité comme null")
        void releveParts_datesVides_traiteeCommeNull() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveParts(eq(compteId), isNull(), isNull()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveParts(compteId, "", "");

            assertThat(r.getStatus()).isEqualTo(200);
            verify(releveService).genererReleveParts(eq(compteId), isNull(), isNull());
        }

        @Test
        @DisplayName("lève IllegalArgumentException si format de date invalide")
        void releveParts_dateFormatInvalide_throwsIllegalArgument() {
            UUID compteId = UUID.randomUUID();

            assertThatThrownBy(() -> resource.releveParts(compteId, "2026-13-01", null))
                    .isInstanceOf(IllegalArgumentException.class)
                    .hasMessageContaining("Format de date invalide");
        }

        @Test
        @DisplayName("lève IllegalArgumentException si dateFin invalide")
        void releveParts_dateFinInvalide_throwsIllegalArgument() {
            UUID compteId = UUID.randomUUID();

            assertThatThrownBy(() -> resource.releveParts(compteId, null, "not-a-date"))
                    .isInstanceOf(IllegalArgumentException.class)
                    .hasMessageContaining("not-a-date");
        }

        @Test
        @DisplayName("propage RuntimeException levée par le service PDF")
        void releveParts_serviceException_propagates() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveParts(any(), any(), any()))
                    .thenThrow(new RuntimeException("Compte parts introuvable"));

            assertThatThrownBy(() -> resource.releveParts(compteId, null, null))
                    .isInstanceOf(RuntimeException.class)
                    .hasMessageContaining("Compte parts introuvable");
        }

        @Test
        @DisplayName("header Content-Disposition contient 'releve-parts' + UUID")
        void releveParts_contentDispositionContientUUID() {
            UUID compteId = UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
            when(releveService.genererReleveParts(any(), any(), any())).thenReturn(PDF_BYTES);

            Response r = resource.releveParts(compteId, null, null);

            assertThat(r.getHeaderString("Content-Disposition"))
                    .contains("releve-parts-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
        }

        @Test
        @DisplayName("retourne 200 avec dates blankes (contiennent espaces)")
        void releveParts_datesBlankes_traiteeCommeNull() {
            UUID compteId = UUID.randomUUID();
            when(releveService.genererReleveParts(eq(compteId), isNull(), isNull()))
                    .thenReturn(PDF_BYTES);

            Response r = resource.releveParts(compteId, "  ", "  ");

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