package dev.lions.unionflow.server.api.dto.evenement.response;

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

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import dev.lions.unionflow.server.api.enums.evenement.PrioriteEvenement;
import dev.lions.unionflow.server.api.enums.evenement.StatutEvenement;
import dev.lions.unionflow.server.api.enums.evenement.TypeEvenementMetier;

/**
 * Tests unitaires pour EvenementResponse.
 * Couvre getAdresseComplete (toutes branches avec séparateur), getTauxPresence,
 * sontInscriptionsOuvertes, estBudgetDepasse, getEcartBudgetaire, et autres méthodes.
 */
@DisplayName("Tests EvenementResponse")
class EvenementResponseTest {

  private static EvenementResponse base() {
    return EvenementResponse.builder()
        .titre("Event")
        .typeEvenement(TypeEvenementMetier.CONFERENCE)
        .statut(StatutEvenement.PLANIFIE)
        .priorite(PrioriteEvenement.NORMALE)
        .dateDebut(LocalDate.now().plusDays(1))
        .dateFin(LocalDate.now().plusDays(1))
        .heureDebut(LocalTime.of(9, 0))
        .heureFin(LocalTime.of(18, 0))
        .capaciteMax(100)
        .participantsInscrits(10)
        .build();
  }

  @Nested
  @DisplayName("getAdresseComplete")
  class GetAdresseComplete {

    @Test
    @DisplayName("retourne chaîne vide quand tout null")
    void testVide() {
      EvenementResponse r = EvenementResponse.builder().build();
      assertThat(r.getAdresseComplete()).isEmpty();
    }

    @Test
    @DisplayName("concatène lieu, adresse, ville, region avec séparateur")
    void testAvecSeparateur() {
      EvenementResponse r = EvenementResponse.builder()
          .lieu("Salle A")
          .adresse("1 rue Test")
          .ville("Paris")
          .region("Île-de-France")
          .build();
      assertThat(r.getAdresseComplete())
          .isEqualTo("Salle A, 1 rue Test, Paris, Île-de-France");
    }

    @Test
    @DisplayName("lieu seul sans virgule")
    void testLieuSeul() {
      EvenementResponse r = EvenementResponse.builder().lieu("Salle A").build();
      assertThat(r.getAdresseComplete()).isEqualTo("Salle A");
    }

    @Test
    @DisplayName("adresse après lieu avec virgule")
    void testLieuEtAdresse() {
      EvenementResponse r = EvenementResponse.builder()
          .lieu("Salle A")
          .adresse("1 rue Test")
          .build();
      assertThat(r.getAdresseComplete()).isEqualTo("Salle A, 1 rue Test");
    }

    @Test
    @DisplayName("ville après adresse avec virgule")
    void testAvecVille() {
      EvenementResponse r = EvenementResponse.builder()
          .lieu("Salle A")
          .ville("Lyon")
          .build();
      assertThat(r.getAdresseComplete()).isEqualTo("Salle A, Lyon");
    }

    @Test
    @DisplayName("ignore champs vides ou blancs")
    void testChampsVides() {
      EvenementResponse r = EvenementResponse.builder()
          .lieu("  ")
          .adresse("")
          .ville("Lyon")
          .build();
      assertThat(r.getAdresseComplete()).isEqualTo("Lyon");
    }
  }

  @Nested
  @DisplayName("getTauxPresence")
  class GetTauxPresence {

    @Test
    @DisplayName("retourne 0 quand participantsInscrits null")
    void testInscritsNull() {
      EvenementResponse r = base();
      r.setParticipantsInscrits(null);
      r.setParticipantsPresents(5);
      assertThat(r.getTauxPresence()).isEqualTo(0);
    }

    @Test
    @DisplayName("retourne 0 quand participantsInscrits 0")
    void testInscritsZero() {
      EvenementResponse r = base();
      r.setParticipantsInscrits(0);
      r.setParticipantsPresents(0);
      assertThat(r.getTauxPresence()).isEqualTo(0);
    }

    @Test
    @DisplayName("retourne 0 quand participantsPresents null")
    void testPresentsNull() {
      EvenementResponse r = base();
      r.setParticipantsInscrits(10);
      r.setParticipantsPresents(null);
      assertThat(r.getTauxPresence()).isEqualTo(0);
    }

    @Test
    @DisplayName("calcule pourcentage présence")
    void testCalcul() {
      EvenementResponse r = base();
      r.setParticipantsInscrits(100);
      r.setParticipantsPresents(75);
      assertThat(r.getTauxPresence()).isEqualTo(75);
    }
  }

  @Nested
  @DisplayName("sontInscriptionsOuvertes")
  class SontInscriptionsOuvertes {

    @Test
    @DisplayName("false quand événement annulé")
    void testAnnule() {
      EvenementResponse r = base();
      r.setStatut(StatutEvenement.ANNULE);
      assertThat(r.sontInscriptionsOuvertes()).isFalse();
    }

    @Test
    @DisplayName("false quand événement terminé")
    void testTermine() {
      EvenementResponse r = base();
      r.setStatut(StatutEvenement.TERMINE);
      assertThat(r.sontInscriptionsOuvertes()).isFalse();
    }

    @Test
    @DisplayName("false quand dateLimiteInscription dépassée")
    void testDateLimitePassee() {
      EvenementResponse r = base();
      r.setDateLimiteInscription(LocalDate.now().minusDays(1));
      assertThat(r.sontInscriptionsOuvertes()).isFalse();
    }

    @Test
    @DisplayName("false quand événement complet")
    void testComplet() {
      EvenementResponse r = base();
      r.setCapaciteMax(10);
      r.setParticipantsInscrits(10);
      assertThat(r.sontInscriptionsOuvertes()).isFalse();
    }

    @Test
    @DisplayName("true quand inscriptions ouvertes")
    void testOuvertes() {
      EvenementResponse r = base();
      r.setDateLimiteInscription(LocalDate.now().plusDays(5));
      r.setCapaciteMax(100);
      r.setParticipantsInscrits(50);
      assertThat(r.sontInscriptionsOuvertes()).isTrue();
    }
  }

  @Nested
  @DisplayName("getDureeEnHeures, estEvenementMultiJours, getTypeEvenementLibelle, getStatutLibelle, getPrioriteLibelle")
  class AutresMethodes {

    @Test
    @DisplayName("getDureeEnHeures retourne 0 si heureDebut ou heureFin null")
    void testDureeNull() {
      EvenementResponse r = base();
      r.setHeureDebut(null);
      assertThat(r.getDureeEnHeures()).isEqualTo(0);
      r.setHeureDebut(LocalTime.of(9, 0));
      r.setHeureFin(null);
      assertThat(r.getDureeEnHeures()).isEqualTo(0);
    }

    @Test
    @DisplayName("getDureeEnHeures calcule la durée")
    void testDuree() {
      EvenementResponse r = base();
      r.setHeureDebut(LocalTime.of(9, 0));
      r.setHeureFin(LocalTime.of(18, 0));
      assertThat(r.getDureeEnHeures()).isEqualTo(9);
    }

    @Test
    @DisplayName("estEvenementMultiJours true quand dateDebut != dateFin")
    void testMultiJours() {
      EvenementResponse r = base();
      r.setDateDebut(LocalDate.now());
      r.setDateFin(LocalDate.now().plusDays(2));
      assertThat(r.estEvenementMultiJours()).isTrue();
    }

    @Test
    @DisplayName("getTypeEvenementLibelle retourne Non défini si type null")
    void testTypeNull() {
      EvenementResponse r = base();
      r.setTypeEvenement(null);
      assertThat(r.getTypeEvenementLibelle()).isEqualTo("Non défini");
    }

    @Test
    @DisplayName("getStatutLibelle retourne Non défini si statut null")
    void testStatutNull() {
      EvenementResponse r = base();
      r.setStatut(null);
      assertThat(r.getStatutLibelle()).isEqualTo("Non défini");
    }

    @Test
    @DisplayName("getPrioriteLibelle retourne Normale si priorite null")
    void testPrioriteNull() {
      EvenementResponse r = base();
      r.setPriorite(null);
      assertThat(r.getPrioriteLibelle()).isEqualTo("Normale");
    }
  }

  @Nested
  @DisplayName("hasCoordonnees, getEcartBudgetaire, estBudgetDepasse")
  class BudgetEtCoordonnees {

    @Test
    @DisplayName("hasCoordonnees true quand latitude et longitude non null")
    void testCoordonnees() {
      EvenementResponse r = base();
      r.setLatitude(BigDecimal.ONE);
      r.setLongitude(BigDecimal.ONE);
      assertThat(r.hasCoordonnees()).isTrue();
    }

    @Test
    @DisplayName("hasCoordonnees false si un des deux null")
    void testCoordonneesNull() {
      EvenementResponse r = base();
      assertThat(r.hasCoordonnees()).isFalse();
      r.setLatitude(BigDecimal.ONE);
      assertThat(r.hasCoordonnees()).isFalse();
    }

    @Test
    @DisplayName("getEcartBudgetaire retourne ZERO si budget ou coutReel null")
    void testEcartNull() {
      EvenementResponse r = base();
      assertThat(r.getEcartBudgetaire()).isEqualTo(BigDecimal.ZERO);
      r.setBudget(BigDecimal.valueOf(1000));
      assertThat(r.getEcartBudgetaire()).isEqualTo(BigDecimal.ZERO);
    }

    @Test
    @DisplayName("getEcartBudgetaire calcule budget - coutReel")
    void testEcart() {
      EvenementResponse r = base();
      r.setBudget(BigDecimal.valueOf(1000));
      r.setCoutReel(BigDecimal.valueOf(800));
      assertThat(r.getEcartBudgetaire()).isEqualByComparingTo(BigDecimal.valueOf(200));
    }

    @Test
    @DisplayName("estBudgetDepasse true quand coutReel > budget")
    void testBudgetDepasse() {
      EvenementResponse r = base();
      r.setBudget(BigDecimal.valueOf(1000));
      r.setCoutReel(BigDecimal.valueOf(1200));
      assertThat(r.estBudgetDepasse()).isTrue();
    }

    @Test
    @DisplayName("estBudgetDepasse false quand budget >= coutReel")
    void testBudgetOk() {
      EvenementResponse r = base();
      r.setBudget(BigDecimal.valueOf(1000));
      r.setCoutReel(BigDecimal.valueOf(1000));
      assertThat(r.estBudgetDepasse()).isFalse();
    }
  }
}
