package dev.lions.unionflow.server.api.dto.dashboard;

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

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

/**
 * Tests unitaires pour UpcomingEventResponse.
 * Couvre getDaysUntilEvent (toutes branches), getFillPercentage, getIsFull, getIsAlmostFull,
 * getIsToday, getIsTomorrow, getStatusColor, getStatusLabel, getAvailableSpots, getParticipationSummary.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-02-21
 */
@DisplayName("Tests UpcomingEventResponse")
class UpcomingEventResponseTest {

  @Nested
  @DisplayName("getDaysUntilEvent")
  class GetDaysUntilEvent {

    @Test
    @DisplayName("retourne chaîne vide quand startDate est null")
    void testStartDateNull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().build();
      assertThat(dto.getDaysUntilEvent()).isEmpty();
    }

    @Test
    @DisplayName("retourne En cours quand événement aujourd'hui déjà commencé (now fixe pour éviter passage à minuit)")
    void testEnCours() {
      LocalDateTime now = LocalDateTime.of(2025, 6, 10, 14, 0);
      LocalDateTime startDate = now.minusHours(1); // même jour, commencé il y a 1 h
      UpcomingEventResponse dto = UpcomingEventResponse.builder().startDate(startDate).build();
      assertThat(dto.getDaysUntilEvent(now)).isEqualTo("En cours");
    }

    @Test
    @DisplayName("retourne En cours quand startDate est la veille (days < 0)")
    void testEnCoursStartDateHier() {
      LocalDateTime now = LocalDateTime.of(2025, 6, 10, 0, 30);
      LocalDateTime startDate = LocalDateTime.of(2025, 6, 9, 23, 0); // veille 23h
      UpcomingEventResponse dto = UpcomingEventResponse.builder().startDate(startDate).build();
      assertThat(dto.getDaysUntilEvent(now)).isEqualTo("En cours");
    }

    @Test
    @DisplayName("retourne Bientôt quand aujourd'hui dans moins de 2h (now fixe pour éviter passage à minuit)")
    void testBientot() {
      LocalDateTime now = LocalDateTime.of(2025, 6, 10, 14, 0);
      LocalDateTime startDate = now.plusMinutes(30);
      UpcomingEventResponse dto = UpcomingEventResponse.builder().startDate(startDate).build();
      assertThat(dto.getDaysUntilEvent(now)).isEqualTo("Bientôt");
    }

    @Test
    @DisplayName("retourne Aujourd'hui quand aujourd'hui dans plus de 2h")
    void testAujourdhui() {
      // Date fixe « aujourd'hui » à 15h pour éviter la dépendance à l'heure d'exécution
      // (now().plusHours(5) peut tomber le lendemain après 19h et renvoyer « Demain »)
      LocalDateTime now = LocalDateTime.now();
      LocalDateTime startDate = LocalDate.now().atTime(15, 0);
      UpcomingEventResponse dto = UpcomingEventResponse.builder().startDate(startDate).build();
      // Utiliser le même 'now' capturé pour éviter les décalages temporels
      String result = dto.getDaysUntilEvent(now);
      // Selon l'heure d'exécution : >= 2h dans le futur → Aujourd'hui, < 2h → Bientôt, passé → En cours
      boolean atLeast2hFromNow = !startDate.isBefore(now.plusHours(2));
      boolean inFuture = startDate.isAfter(now);
      if (atLeast2hFromNow) {
        assertThat(result).isEqualTo("Aujourd'hui");
      } else if (inFuture) {
        assertThat(result).isEqualTo("Bientôt");
      } else {
        assertThat(result).isEqualTo("En cours");
      }
    }

    @Test
    @DisplayName("retourne Aujourd'hui quand même jour et plus de 2h dans le futur (appel avec now fixe pour couverture Jacoco)")
    void testAujourdhuiAvecNowFixe() {
      LocalDateTime now = LocalDateTime.of(2025, 6, 10, 10, 0);
      LocalDateTime startDate = LocalDateTime.of(2025, 6, 10, 15, 0);
      UpcomingEventResponse dto = UpcomingEventResponse.builder().startDate(startDate).build();
      assertThat(dto.getDaysUntilEvent(now)).isEqualTo("Aujourd'hui");
    }

    @Test
    @DisplayName("retourne Demain quand startDate est demain")
    void testDemain() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .startDate(LocalDateTime.now().plusDays(1)).build();
      assertThat(dto.getDaysUntilEvent()).isEqualTo("Demain");
    }

    @Test
    @DisplayName("retourne Dans X jours quand moins de 7 jours")
    void testDansJours() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .startDate(LocalDateTime.now().plusDays(3)).build();
      assertThat(dto.getDaysUntilEvent()).isEqualTo("Dans 3 jours");
    }

    @Test
    @DisplayName("retourne Dans 1 semaine quand 7 jours ou plus (jours < 7 false)")
    void testUneSemaine() {
      LocalDateTime now = LocalDateTime.now();
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .startDate(now.plusDays(8)).build();
      String result = dto.getDaysUntilEvent();
      assertThat(result).isEqualTo("Dans 1 semaine");
    }

    @Test
    @DisplayName("retourne Dans X semaines quand au moins 14 jours")
    void testSemaines() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .startDate(LocalDateTime.now().plusDays(15)).build();
      assertThat(dto.getDaysUntilEvent()).isEqualTo("Dans 2 semaines");
    }
  }

  @Nested
  @DisplayName("getFillPercentage")
  class GetFillPercentage {

    @Test
    @DisplayName("retourne 0 quand maxParticipants ou currentParticipants est null")
    void testNull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().maxParticipants(10).build();
      assertThat(dto.getFillPercentage()).isEqualTo(0.0);
      dto.setCurrentParticipants(5);
      dto.setMaxParticipants(null);
      assertThat(dto.getFillPercentage()).isEqualTo(0.0);
    }

    @Test
    @DisplayName("retourne 0 quand maxParticipants est 0")
    void testMaxZero() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .maxParticipants(0).currentParticipants(0).build();
      assertThat(dto.getFillPercentage()).isEqualTo(0.0);
    }

    @Test
    @DisplayName("retourne le pourcentage de remplissage")
    void testPourcentage() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .maxParticipants(10).currentParticipants(4).build();
      assertThat(dto.getFillPercentage()).isEqualTo(40.0);
    }
  }

  @Nested
  @DisplayName("getIsFull")
  class GetIsFull {

    @Test
    @DisplayName("retourne false quand max ou current null")
    void testNull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().build();
      assertThat(dto.getIsFull()).isFalse();
    }

    @Test
    @DisplayName("retourne true quand current >= max")
    void testFull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .maxParticipants(5).currentParticipants(5).build();
      assertThat(dto.getIsFull()).isTrue();
    }

    @Test
    @DisplayName("retourne false quand current < max")
    void testNotFull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .maxParticipants(5).currentParticipants(3).build();
      assertThat(dto.getIsFull()).isFalse();
    }
  }

  @Nested
  @DisplayName("getIsAlmostFull")
  class GetIsAlmostFull {

    @Test
    @DisplayName("retourne true quand fillPercentage entre 80 et 100")
    void testAlmostFull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .maxParticipants(10).currentParticipants(9).build();
      assertThat(dto.getIsAlmostFull()).isTrue();
    }

    @Test
    @DisplayName("retourne false quand fillPercentage < 80")
    void testNotAlmostFull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .maxParticipants(10).currentParticipants(5).build();
      assertThat(dto.getIsAlmostFull()).isFalse();
    }

    @Test
    @DisplayName("retourne false quand fillPercentage 100")
    void testFullNotAlmostFull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .maxParticipants(10).currentParticipants(10).build();
      assertThat(dto.getIsAlmostFull()).isFalse();
    }
  }

  @Nested
  @DisplayName("getIsToday et getIsTomorrow")
  class GetIsTodayTomorrow {

    @Test
    @DisplayName("getIsToday retourne false quand startDate null")
    void testIsTodayNull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().build();
      assertThat(dto.getIsToday()).isFalse();
    }

    @Test
    @DisplayName("getIsToday retourne true quand startDate aujourd'hui")
    void testIsTodayTrue() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .startDate(LocalDateTime.now()).build();
      assertThat(dto.getIsToday()).isTrue();
    }

    @Test
    @DisplayName("getIsTomorrow retourne false quand startDate null")
    void testIsTomorrowNull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().build();
      assertThat(dto.getIsTomorrow()).isFalse();
    }

    @Test
    @DisplayName("getIsTomorrow retourne true quand startDate demain")
    void testIsTomorrowTrue() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .startDate(LocalDateTime.now().plusDays(1)).build();
      assertThat(dto.getIsTomorrow()).isTrue();
    }
  }

  @Nested
  @DisplayName("getStatusColor")
  class GetStatusColor {

    @Test
    @DisplayName("retourne grey quand status null")
    void testNull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().build();
      assertThat(dto.getStatusColor()).isEqualTo("#6B7280");
    }

    @Test
    @DisplayName("retourne couleur selon status confirmed")
    void testConfirmed() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().status("confirmed").build();
      assertThat(dto.getStatusColor()).isEqualTo("#10B981");
    }

    @Test
    @DisplayName("retourne couleur selon status open")
    void testOpen() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().status("open").build();
      assertThat(dto.getStatusColor()).isEqualTo("#3B82F6");
    }

    @Test
    @DisplayName("retourne couleur selon status cancelled")
    void testCancelled() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().status("cancelled").build();
      assertThat(dto.getStatusColor()).isEqualTo("#EF4444");
    }

    @Test
    @DisplayName("retourne couleur selon status postponed")
    void testPostponed() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().status("postponed").build();
      assertThat(dto.getStatusColor()).isEqualTo("#F59E0B");
    }

    @Test
    @DisplayName("retourne grey pour status inconnu")
    void testDefault() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().status("unknown").build();
      assertThat(dto.getStatusColor()).isEqualTo("#6B7280");
    }

    @Test
    @DisplayName("status insensible à la casse")
    void testCaseInsensitive() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().status("CONFIRMED").build();
      assertThat(dto.getStatusColor()).isEqualTo("#10B981");
    }
  }

  @Nested
  @DisplayName("getStatusLabel")
  class GetStatusLabel {

    @Test
    @DisplayName("retourne Inconnu quand status null")
    void testNull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().build();
      assertThat(dto.getStatusLabel()).isEqualTo("Inconnu");
    }

    @Test
    @DisplayName("retourne libellé pour confirmed, open, cancelled, postponed")
    void testLabels() {
      assertThat(UpcomingEventResponse.builder().status("confirmed").build().getStatusLabel()).isEqualTo("Confirmé");
      assertThat(UpcomingEventResponse.builder().status("open").build().getStatusLabel()).isEqualTo("Ouvert");
      assertThat(UpcomingEventResponse.builder().status("cancelled").build().getStatusLabel()).isEqualTo("Annulé");
      assertThat(UpcomingEventResponse.builder().status("postponed").build().getStatusLabel()).isEqualTo("Reporté");
    }

    @Test
    @DisplayName("retourne status tel quel pour valeur inconnue")
    void testDefault() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().status("custom").build();
      assertThat(dto.getStatusLabel()).isEqualTo("custom");
    }
  }

  @Nested
  @DisplayName("getAvailableSpots")
  class GetAvailableSpots {

    @Test
    @DisplayName("retourne 0 quand max ou current null")
    void testNull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().build();
      assertThat(dto.getAvailableSpots()).isEqualTo(0);
    }

    @Test
    @DisplayName("retourne max - current")
    void testCalcul() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .maxParticipants(10).currentParticipants(7).build();
      assertThat(dto.getAvailableSpots()).isEqualTo(3);
    }

    @Test
    @DisplayName("retourne 0 quand current > max (Math.max)")
    void testNegatif() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .maxParticipants(5).currentParticipants(10).build();
      assertThat(dto.getAvailableSpots()).isEqualTo(0);
    }
  }

  @Nested
  @DisplayName("getParticipationSummary")
  class GetParticipationSummary {

    @Test
    @DisplayName("retourne 0/0 quand max ou current null")
    void testNull() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder().build();
      assertThat(dto.getParticipationSummary()).isEqualTo("0/0 participants");
    }

    @Test
    @DisplayName("retourne current/max participants")
    void testSummary() {
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .maxParticipants(20).currentParticipants(12).build();
      assertThat(dto.getParticipationSummary()).isEqualTo("12/20 participants");
    }
  }

  @Nested
  @DisplayName("Getters standards")
  class GettersStandards {

    @Test
    @DisplayName("tous les champs builder sont accessibles")
    void testBuilderGetters() {
      LocalDateTime start = LocalDateTime.now();
      LocalDateTime end = start.plusHours(2);
      UpcomingEventResponse dto = UpcomingEventResponse.builder()
          .id("ev1")
          .title("Titre")
          .description("Desc")
          .startDate(start)
          .endDate(end)
          .location("Paris")
          .maxParticipants(50)
          .currentParticipants(10)
          .status("open")
          .imageUrl("http://img")
          .tags(List.of("a", "b"))
          .build();
      assertThat(dto.getId()).isEqualTo("ev1");
      assertThat(dto.getTitle()).isEqualTo("Titre");
      assertThat(dto.getDescription()).isEqualTo("Desc");
      assertThat(dto.getStartDate()).isEqualTo(start);
      assertThat(dto.getEndDate()).isEqualTo(end);
      assertThat(dto.getLocation()).isEqualTo("Paris");
      assertThat(dto.getMaxParticipants()).isEqualTo(50);
      assertThat(dto.getCurrentParticipants()).isEqualTo(10);
      assertThat(dto.getStatus()).isEqualTo("open");
      assertThat(dto.getImageUrl()).isEqualTo("http://img");
      assertThat(dto.getTags()).containsExactly("a", "b");
    }
  }
}
