package dev.lions.unionflow.server.service.reporting;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.lions.unionflow.server.entity.ReferentielComptable;
import dev.lions.unionflow.server.service.compliance.ComplianceDashboardService.ComplianceSnapshot;
import dev.lions.unionflow.server.service.compliance.ComplianceDashboardService.ConformiteIndicateur;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class RapportTrimestrielServiceTest {

  private final RapportTrimestrielService service = new RapportTrimestrielService();
  private final ObjectMapper mapper = new ObjectMapper();

  // ────────────────────────────────────────────────────────────
  // Helpers statiques — calcul trimestre
  // ────────────────────────────────────────────────────────────

  @Test
  @DisplayName("debutTrimestre — T1 = 1er janvier")
  void debutTrimestre_t1() {
    assertThat(RapportTrimestrielService.debutTrimestre(2026, 1))
        .isEqualTo(LocalDate.of(2026, 1, 1));
  }

  @Test
  @DisplayName("debutTrimestre — T2 = 1er avril")
  void debutTrimestre_t2() {
    assertThat(RapportTrimestrielService.debutTrimestre(2026, 2))
        .isEqualTo(LocalDate.of(2026, 4, 1));
  }

  @Test
  @DisplayName("debutTrimestre — T3 = 1er juillet")
  void debutTrimestre_t3() {
    assertThat(RapportTrimestrielService.debutTrimestre(2026, 3))
        .isEqualTo(LocalDate.of(2026, 7, 1));
  }

  @Test
  @DisplayName("debutTrimestre — T4 = 1er octobre")
  void debutTrimestre_t4() {
    assertThat(RapportTrimestrielService.debutTrimestre(2026, 4))
        .isEqualTo(LocalDate.of(2026, 10, 1));
  }

  @Test
  @DisplayName("finTrimestre — T1 = 31 mars")
  void finTrimestre_t1() {
    assertThat(RapportTrimestrielService.finTrimestre(2026, 1))
        .isEqualTo(LocalDate.of(2026, 3, 31));
  }

  @Test
  @DisplayName("finTrimestre — T4 = 31 décembre")
  void finTrimestre_t4() {
    assertThat(RapportTrimestrielService.finTrimestre(2026, 4))
        .isEqualTo(LocalDate.of(2026, 12, 31));
  }

  @Test
  @DisplayName("debutTrimestre — trimestre invalide → exception")
  void debutTrimestre_invalide() {
    assertThatThrownBy(() -> RapportTrimestrielService.debutTrimestre(2026, 5))
        .isInstanceOf(IllegalArgumentException.class);
  }

  // ────────────────────────────────────────────────────────────
  // Hash SHA-256
  // ────────────────────────────────────────────────────────────

  @Test
  @DisplayName("calculerHash — null → hash de chaîne vide (constant)")
  void hash_null() {
    String h = RapportTrimestrielService.calculerHash(null);
    assertThat(h).hasSize(64);
    // SHA-256 de "" est connu
    assertThat(h).isEqualTo("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
  }

  @Test
  @DisplayName("calculerHash — déterministe pour le même contenu")
  void hash_deterministe() {
    JsonNode n1 = mapper.createObjectNode().put("a", 1).put("b", "x");
    JsonNode n2 = mapper.createObjectNode().put("a", 1).put("b", "x");
    assertThat(RapportTrimestrielService.calculerHash(n1))
        .isEqualTo(RapportTrimestrielService.calculerHash(n2));
  }

  @Test
  @DisplayName("calculerHash — différent pour contenus différents")
  void hash_different() {
    JsonNode n1 = mapper.createObjectNode().put("a", 1);
    JsonNode n2 = mapper.createObjectNode().put("a", 2);
    assertThat(RapportTrimestrielService.calculerHash(n1))
        .isNotEqualTo(RapportTrimestrielService.calculerHash(n2));
  }

  // ────────────────────────────────────────────────────────────
  // Sérialisation JSON + génération PDF (sans EM)
  // ────────────────────────────────────────────────────────────

  private RapportTrimestrielService.DonneesAgregees sampleDonnees() {
    var snapshot = new ComplianceSnapshot(
        UUID.randomUUID(),
        "Mutuelle Test",
        ReferentielComptable.SYSCOHADA,
        true,
        new ConformiteIndicateur("OK", "AG OK"),
        new ConformiteIndicateur("OK", "AIRMS OK"),
        3,
        new BigDecimal("85.00"),
        new BigDecimal("70.00"),
        new ConformiteIndicateur("OPTIONNEL", "CAC optionnel"),
        new ConformiteIndicateur("EN_VEILLE", "FOMUS-CI veille"),
        new BigDecimal("60.00"),
        82
    );
    return new RapportTrimestrielService.DonneesAgregees(
        snapshot.organisationId(), 2026, 1,
        LocalDate.of(2026, 1, 1), LocalDate.of(2026, 3, 31),
        snapshot, 2L, 1L, 0L, 12L);
  }

  @Test
  @DisplayName("construireJson — contient les champs clés")
  void json_contientChamps() {
    var donnees = sampleDonnees();
    JsonNode json = service.construireJson(donnees);

    assertThat(json.get("annee").asInt()).isEqualTo(2026);
    assertThat(json.get("trimestre").asInt()).isEqualTo(1);
    assertThat(json.path("conformite").path("score_global").asInt()).isEqualTo(82);
    assertThat(json.path("activite_trimestrielle").path("dos_centif_emises").asLong()).isEqualTo(2L);
    assertThat(json.path("activite_trimestrielle").path("sessions_formation_lbcft").asLong())
        .isEqualTo(1L);
    assertThat(json.path("alertes").isArray()).isTrue();
  }

  @Test
  @DisplayName("construireJson — alertes vides quand tout OK et score >=60")
  void json_alertesVidesSiToutOK() {
    var donnees = sampleDonnees();
    JsonNode json = service.construireJson(donnees);
    assertThat(json.path("alertes").size()).isZero();
  }

  @Test
  @DisplayName("construireJson — alerte CRITIQUE si compliance officer absent")
  void json_alerteComplianceOfficerAbsent() {
    var s = sampleDonnees().snapshot();
    var snapshotKO = new ComplianceSnapshot(
        s.organisationId(), s.organisationNom(), s.referentielComptable(),
        false, // ← absent
        s.agAnnuelle(), s.rapportAirms(), s.dirigeantsAvecCmu(),
        s.tauxKycAJourPct(), s.tauxFormationLbcFtPct(),
        s.commissaireAuxComptes(), s.fomusCi(), s.couvertureUboPct(), s.scoreGlobal());
    var donnees = new RapportTrimestrielService.DonneesAgregees(
        snapshotKO.organisationId(), 2026, 1,
        LocalDate.of(2026, 1, 1), LocalDate.of(2026, 3, 31),
        snapshotKO, 0L, 0L, 0L, 0L);

    JsonNode json = service.construireJson(donnees);
    assertThat(json.path("alertes").size()).isEqualTo(1);
    assertThat(json.path("alertes").get(0).asText()).contains("Compliance Officer non désigné");
  }

  @Test
  @DisplayName("construireJson — alerte score <60")
  void json_alerteScoreFaible() {
    var s = sampleDonnees().snapshot();
    var snapshotFaible = new ComplianceSnapshot(
        s.organisationId(), s.organisationNom(), s.referentielComptable(),
        s.complianceOfficerDesigne(), s.agAnnuelle(), s.rapportAirms(), s.dirigeantsAvecCmu(),
        s.tauxKycAJourPct(), s.tauxFormationLbcFtPct(),
        s.commissaireAuxComptes(), s.fomusCi(), s.couvertureUboPct(),
        45);
    var donnees = new RapportTrimestrielService.DonneesAgregees(
        snapshotFaible.organisationId(), 2026, 1,
        LocalDate.of(2026, 1, 1), LocalDate.of(2026, 3, 31),
        snapshotFaible, 0L, 0L, 0L, 0L);
    JsonNode json = service.construireJson(donnees);
    assertThat(json.path("alertes").size()).isEqualTo(1);
    assertThat(json.path("alertes").get(0).asText()).contains("Score conformité <60%");
  }

  @Test
  @DisplayName("genererPdf — produit un PDF non vide")
  void pdf_nonVide() {
    byte[] pdf = service.genererPdf(sampleDonnees());
    assertThat(pdf).isNotEmpty();
    assertThat(pdf.length).isGreaterThan(500);
    // Header PDF magique
    assertThat(new String(pdf, 0, 5)).startsWith("%PDF-");
  }
}
