package dev.lions.unionflow.server.resource;

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.when;

import dev.lions.unionflow.server.service.ComptabilitePdfService;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.DisplayName;
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.time.LocalDate;
import java.util.UUID;

/**
 * Tests unitaires Mockito pour {@link ComptabilitePdfResource}.
 *
 * <p>Couvre tous les chemins d'exécution :
 * <ul>
 *   <li>balance/compteDeResultat/grandLivre avec dates valides → 200 avec PDF</li>
 *   <li>dates vides/null → parseDateOrStartOfYear/parseDateOrToday → dates par défaut</li>
 *   <li>dates invalides → BadRequestException (branche catch)</li>
 *   <li>Content-Disposition et Content-Length présents dans les headers</li>
 * </ul>
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("Tests ComptabilitePdfResource")
class ComptabilitePdfResourceTest {

    @Mock
    ComptabilitePdfService comptabilitePdfService;

    @InjectMocks
    ComptabilitePdfResource resource;

    private static final UUID ORG_ID = UUID.fromString("00000000-0000-0000-0000-000000000001");
    private static final byte[] DUMMY_PDF = new byte[]{0x25, 0x50, 0x44, 0x46}; // %PDF magic bytes

    // ── GET /api/comptabilite/pdf/organisations/{id}/balance ──────────────────

    @Test
    @DisplayName("balance — dates valides → 200 avec PDF et headers Content-Disposition/Content-Length")
    void balance_validDates_returns200WithPdfHeaders() {
        when(comptabilitePdfService.genererBalance(eq(ORG_ID), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(DUMMY_PDF);

        Response response = resource.balance(ORG_ID, "2026-01-01", "2026-03-31");

        assertThat(response.getStatus()).isEqualTo(200);
        assertThat(response.getEntity()).isEqualTo(DUMMY_PDF);
        assertThat(response.getHeaderString("Content-Disposition"))
                .contains("balance_" + ORG_ID + ".pdf");
        assertThat(response.getHeaderString("Content-Length"))
                .isEqualTo(String.valueOf(DUMMY_PDF.length));
    }

    @Test
    @DisplayName("balance — dates vides → utilise début d'année et aujourd'hui → 200")
    void balance_emptyDates_usesDefaultDates_returns200() {
        when(comptabilitePdfService.genererBalance(eq(ORG_ID), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(DUMMY_PDF);

        Response response = resource.balance(ORG_ID, "", "");

        assertThat(response.getStatus()).isEqualTo(200);
        assertThat(response.getEntity()).isEqualTo(DUMMY_PDF);
    }

    @Test
    @DisplayName("balance — dateDebut invalide → BadRequestException")
    void balance_invalidDateDebut_throwsBadRequestException() {
        assertThatThrownBy(() -> resource.balance(ORG_ID, "not-a-date", "2026-03-31"))
                .isInstanceOf(BadRequestException.class)
                .hasMessageContaining("Format de date invalide");
    }

    @Test
    @DisplayName("balance — dateFin invalide → BadRequestException")
    void balance_invalidDateFin_throwsBadRequestException() {
        assertThatThrownBy(() -> resource.balance(ORG_ID, "2026-01-01", "31/12/2026"))
                .isInstanceOf(BadRequestException.class)
                .hasMessageContaining("Format de date invalide");
    }

    @Test
    @DisplayName("balance — dateDebut null → utilise début d'année → 200")
    void balance_nullDateDebut_usesStartOfYear_returns200() {
        when(comptabilitePdfService.genererBalance(eq(ORG_ID), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(DUMMY_PDF);

        Response response = resource.balance(ORG_ID, null, null);

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

    @Test
    @DisplayName("balance — PDF vide (0 octets) → 200 avec Content-Length=0")
    void balance_emptyPdf_returns200WithZeroContentLength() {
        byte[] emptyPdf = new byte[0];
        when(comptabilitePdfService.genererBalance(eq(ORG_ID), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(emptyPdf);

        Response response = resource.balance(ORG_ID, "2026-01-01", "2026-12-31");

        assertThat(response.getStatus()).isEqualTo(200);
        assertThat(response.getHeaderString("Content-Length")).isEqualTo("0");
    }

    // ── GET /api/comptabilite/pdf/organisations/{id}/compte-de-resultat ───────

    @Test
    @DisplayName("compteDeResultat — dates valides → 200 avec nom fichier correct")
    void compteDeResultat_validDates_returns200WithFilename() {
        when(comptabilitePdfService.genererCompteResultat(eq(ORG_ID), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(DUMMY_PDF);

        Response response = resource.compteDeResultat(ORG_ID, "2026-01-01", "2026-12-31");

        assertThat(response.getStatus()).isEqualTo(200);
        assertThat(response.getEntity()).isEqualTo(DUMMY_PDF);
        assertThat(response.getHeaderString("Content-Disposition"))
                .contains("compte_resultat_" + ORG_ID + ".pdf");
    }

    @Test
    @DisplayName("compteDeResultat — dates vides → 200 avec dates par défaut")
    void compteDeResultat_emptyDates_usesDefaultDates_returns200() {
        when(comptabilitePdfService.genererCompteResultat(eq(ORG_ID), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(DUMMY_PDF);

        Response response = resource.compteDeResultat(ORG_ID, "", "");

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

    @Test
    @DisplayName("compteDeResultat — dateDebut invalide → BadRequestException")
    void compteDeResultat_invalidDateDebut_throwsBadRequestException() {
        assertThatThrownBy(() -> resource.compteDeResultat(ORG_ID, "2026/01/01", "2026-12-31"))
                .isInstanceOf(BadRequestException.class)
                .hasMessageContaining("YYYY-MM-DD");
    }

    @Test
    @DisplayName("compteDeResultat — dateFin invalide → BadRequestException")
    void compteDeResultat_invalidDateFin_throwsBadRequestException() {
        assertThatThrownBy(() -> resource.compteDeResultat(ORG_ID, "2026-01-01", "foobar"))
                .isInstanceOf(BadRequestException.class)
                .hasMessageContaining("foobar");
    }

    @Test
    @DisplayName("compteDeResultat — dates null → 200 avec dates par défaut")
    void compteDeResultat_nullDates_usesDefaultDates_returns200() {
        when(comptabilitePdfService.genererCompteResultat(eq(ORG_ID), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(DUMMY_PDF);

        Response response = resource.compteDeResultat(ORG_ID, null, null);

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

    // ── GET /api/comptabilite/pdf/organisations/{id}/grand-livre/{compte} ─────

    @Test
    @DisplayName("grandLivre — dates et compte valides → 200 avec nom fichier basé sur numéroCompte")
    void grandLivre_validInput_returns200WithFilename() {
        String numeroCompte = "601000";
        when(comptabilitePdfService.genererGrandLivre(
                eq(ORG_ID), eq(numeroCompte), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(DUMMY_PDF);

        Response response = resource.grandLivre(ORG_ID, numeroCompte, "2026-01-01", "2026-12-31");

        assertThat(response.getStatus()).isEqualTo(200);
        assertThat(response.getEntity()).isEqualTo(DUMMY_PDF);
        assertThat(response.getHeaderString("Content-Disposition"))
                .contains("grand_livre_" + numeroCompte + ".pdf");
        assertThat(response.getHeaderString("Content-Length"))
                .isEqualTo(String.valueOf(DUMMY_PDF.length));
    }

    @Test
    @DisplayName("grandLivre — dates vides → 200 avec dates par défaut")
    void grandLivre_emptyDates_usesDefaultDates_returns200() {
        String numeroCompte = "512100";
        when(comptabilitePdfService.genererGrandLivre(
                eq(ORG_ID), eq(numeroCompte), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(DUMMY_PDF);

        Response response = resource.grandLivre(ORG_ID, numeroCompte, "", "");

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

    @Test
    @DisplayName("grandLivre — dateDebut invalide → BadRequestException")
    void grandLivre_invalidDateDebut_throwsBadRequestException() {
        assertThatThrownBy(() -> resource.grandLivre(ORG_ID, "601000", "99-99-99", "2026-12-31"))
                .isInstanceOf(BadRequestException.class)
                .hasMessageContaining("Format de date invalide");
    }

    @Test
    @DisplayName("grandLivre — dateFin invalide → BadRequestException")
    void grandLivre_invalidDateFin_throwsBadRequestException() {
        assertThatThrownBy(() -> resource.grandLivre(ORG_ID, "601000", "2026-01-01", "invalid"))
                .isInstanceOf(BadRequestException.class)
                .hasMessageContaining("invalid");
    }

    @Test
    @DisplayName("grandLivre — dates null → 200 avec dates par défaut")
    void grandLivre_nullDates_usesDefaultDates_returns200() {
        String numeroCompte = "401000";
        when(comptabilitePdfService.genererGrandLivre(
                eq(ORG_ID), eq(numeroCompte), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(DUMMY_PDF);

        Response response = resource.grandLivre(ORG_ID, numeroCompte, null, null);

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

    @Test
    @DisplayName("grandLivre — grand PDF (1 Mo) → Content-Length correct")
    void grandLivre_largePdf_correctContentLength() {
        String numeroCompte = "411000";
        byte[] largePdf = new byte[1024 * 1024]; // 1 MB
        when(comptabilitePdfService.genererGrandLivre(
                eq(ORG_ID), eq(numeroCompte), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(largePdf);

        Response response = resource.grandLivre(ORG_ID, numeroCompte, "2026-01-01", "2026-12-31");

        assertThat(response.getStatus()).isEqualTo(200);
        assertThat(response.getHeaderString("Content-Length"))
                .isEqualTo(String.valueOf(1024 * 1024));
    }

    // ── Vérification parseDateOrStartOfYear — branche : date valide parsée ───

    @Test
    @DisplayName("balance — date de début 2025-06-15 parsée correctement → utilise 2025-06-15 dans l'appel service")
    void balance_specificDateDebut_parsedCorrectly() {
        when(comptabilitePdfService.genererBalance(
                eq(ORG_ID),
                eq(LocalDate.of(2025, 6, 15)),
                eq(LocalDate.of(2025, 12, 31))))
                .thenReturn(DUMMY_PDF);

        Response response = resource.balance(ORG_ID, "2025-06-15", "2025-12-31");

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