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

import static dev.lions.unionflow.server.api.TestDataFactory.createUpcomingEventDTO;
import static dev.lions.unionflow.server.api.TestDataFactory.daysFromNow;
import static dev.lions.unionflow.server.api.TestDataFactory.hoursAgo;
import static dev.lions.unionflow.server.api.TestDataFactory.now;
import static org.assertj.core.api.Assertions.assertThat;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

@DisplayName("Tests pour UpcomingEventDTO")
class UpcomingEventDTOTest {

  @Test
  @DisplayName("Test de base - classe peut être instanciée")
  void testClasseInstanciable() {
    UpcomingEventDTO dto = new UpcomingEventDTO();
    assertThat(dto).isNotNull();
  }

  @Nested
  @DisplayName("Tests de construction")
  class ConstructionTests {

    @Test
    @DisplayName("Constructeur par défaut")
    void testConstructeurParDefaut() {
      UpcomingEventDTO dto = new UpcomingEventDTO();

      assertThat(dto).isNotNull();
    }

    @Test
    @DisplayName("Builder pattern")
    void testBuilder() {
      LocalDateTime startDate = now().plusDays(1);
      LocalDateTime endDate = startDate.plusHours(2);

      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .id("event-123")
          .title("Test Event")
          .description("Test Description")
          .startDate(startDate)
          .endDate(endDate)
          .location("Test Location")
          .maxParticipants(100)
          .currentParticipants(50)
          .status("open")
          .imageUrl("http://example.com/image.jpg")
          .tags(List.of("tag1", "tag2"))
          .build();

      assertThat(dto.getId()).isEqualTo("event-123");
      assertThat(dto.getTitle()).isEqualTo("Test Event");
      assertThat(dto.getDescription()).isEqualTo("Test Description");
      assertThat(dto.getStartDate()).isEqualTo(startDate);
      assertThat(dto.getEndDate()).isEqualTo(endDate);
      assertThat(dto.getLocation()).isEqualTo("Test Location");
      assertThat(dto.getMaxParticipants()).isEqualTo(100);
      assertThat(dto.getCurrentParticipants()).isEqualTo(50);
      assertThat(dto.getStatus()).isEqualTo("open");
      assertThat(dto.getImageUrl()).isEqualTo("http://example.com/image.jpg");
      assertThat(dto.getTags()).containsExactly("tag1", "tag2");
    }
  }

  @Nested
  @DisplayName("Tests getDaysUntilEvent")
  class GetDaysUntilEventTests {

    @Test
    @DisplayName("getDaysUntilEvent - aujourd'hui (bientôt)")
    void testGetDaysUntilEventAujourdhuiBientot() {
      UpcomingEventDTO dto = createUpcomingEventDTO(now().plusHours(1));

      assertThat(dto.getDaysUntilEvent()).isEqualTo("Bientôt");
    }

    @Test
    @DisplayName("getDaysUntilEvent - aujourd'hui (en cours)")
    void testGetDaysUntilEventAujourdhuiEnCours() {
      UpcomingEventDTO dto = createUpcomingEventDTO(hoursAgo(1));

      assertThat(dto.getDaysUntilEvent()).isEqualTo("En cours");
    }

    @Test
    @DisplayName("getDaysUntilEvent - aujourd'hui (dans 2h)")
    void testGetDaysUntilEventAujourdhuiDans2h() {
      UpcomingEventDTO dto = createUpcomingEventDTO(now().plusHours(3));

      assertThat(dto.getDaysUntilEvent()).isEqualTo("Aujourd'hui");
    }

    @Test
    @DisplayName("getDaysUntilEvent - demain")
    void testGetDaysUntilEventDemain() {
      // Utiliser une date qui est garantie d'être demain (au moins 25 heures dans le futur)
      // pour éviter les problèmes de timing si le test s'exécute très tard dans la journée
      LocalDateTime maintenant = now();
      LocalDateTime demain = maintenant.plusDays(1).plusHours(1);
      
      // S'assurer que c'est bien demain (au moins 25h dans le futur pour être sûr)
      if (ChronoUnit.HOURS.between(maintenant, demain) < 25) {
        demain = maintenant.plusDays(1).plusHours(2);
      }
      
      UpcomingEventDTO dto = createUpcomingEventDTO(demain);
      
      // Le résultat devrait être "Demain" car on est au moins 25h dans le futur
      assertThat(dto.getDaysUntilEvent()).isEqualTo("Demain");
    }

    @Test
    @DisplayName("getDaysUntilEvent - dans quelques jours")
    void testGetDaysUntilEventDansQuelquesJours() {
      UpcomingEventDTO dto = createUpcomingEventDTO(daysFromNow(3));

      // Le calcul peut donner "Dans 2 jours" ou "Dans 3 jours" selon le moment de la journée
      assertThat(dto.getDaysUntilEvent()).matches("Dans [23] jours");
    }

    @Test
    @DisplayName("getDaysUntilEvent - dans plusieurs semaines")
    void testGetDaysUntilEventDansPlusieursSemaines() {
      UpcomingEventDTO dto = createUpcomingEventDTO(daysFromNow(14));

      // Le calcul peut donner "Dans 1 semaine" ou "Dans 2 semaines" selon le moment de la journée
      assertThat(dto.getDaysUntilEvent()).matches("Dans [12] semaine[s]?");
    }

    @Test
    @DisplayName("getDaysUntilEvent - startDate null")
    void testGetDaysUntilEventStartDateNull() {
      UpcomingEventDTO dto = new UpcomingEventDTO();

      assertThat(dto.getDaysUntilEvent()).isEmpty();
    }
  }

  @Nested
  @DisplayName("Tests getFillPercentage")
  class GetFillPercentageTests {

    @ParameterizedTest
    @CsvSource({
        "100, 50, 50.0",
        "100, 100, 100.0",
        "100, 0, 0.0",
        "200, 100, 50.0"
    })
    @DisplayName("getFillPercentage - calculs variés")
    void testGetFillPercentage(Integer max, Integer current, Double expected) {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .maxParticipants(max)
          .currentParticipants(current)
          .build();

      assertThat(dto.getFillPercentage()).isEqualTo(expected);
    }

    @Test
    @DisplayName("getFillPercentage - maxParticipants null")
    void testGetFillPercentageMaxNull() {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .currentParticipants(50)
          .build();

      assertThat(dto.getFillPercentage()).isEqualTo(0.0);
    }

    @Test
    @DisplayName("getFillPercentage - currentParticipants null")
    void testGetFillPercentageCurrentNull() {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .maxParticipants(100)
          .build();

      assertThat(dto.getFillPercentage()).isEqualTo(0.0);
    }

    @Test
    @DisplayName("getFillPercentage - maxParticipants = 0")
    void testGetFillPercentageMaxZero() {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .maxParticipants(0)
          .currentParticipants(50)
          .build();

      assertThat(dto.getFillPercentage()).isEqualTo(0.0);
    }
  }

  @Nested
  @DisplayName("Tests getIsFull")
  class GetIsFullTests {

    @Test
    @DisplayName("getIsFull - complet")
    void testGetIsFullComplet() {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .maxParticipants(100)
          .currentParticipants(100)
          .build();

      assertThat(dto.getIsFull()).isTrue();
    }

    @Test
    @DisplayName("getIsFull - dépassé")
    void testGetIsFullDepasse() {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .maxParticipants(100)
          .currentParticipants(150)
          .build();

      assertThat(dto.getIsFull()).isTrue();
    }

    @Test
    @DisplayName("getIsFull - pas complet")
    void testGetIsFullPasComplet() {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .maxParticipants(100)
          .currentParticipants(50)
          .build();

      assertThat(dto.getIsFull()).isFalse();
    }

    @Test
    @DisplayName("getIsFull - null")
    void testGetIsFullNull() {
      UpcomingEventDTO dto = new UpcomingEventDTO();

      assertThat(dto.getIsFull()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests getIsAlmostFull")
  class GetIsAlmostFullTests {

    @ParameterizedTest
    @CsvSource({
        "100, 80, true",
        "100, 79, false",
        "100, 100, false",
        "100, 99, true"
    })
    @DisplayName("getIsAlmostFull - seuil 80%")
    void testGetIsAlmostFull(Integer max, Integer current, Boolean expected) {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .maxParticipants(max)
          .currentParticipants(current)
          .build();

      assertThat(dto.getIsAlmostFull()).isEqualTo(expected);
    }
  }

  @Nested
  @DisplayName("Tests getIsToday et getIsTomorrow")
  class GetIsTodayTomorrowTests {

    @Test
    @DisplayName("getIsToday - aujourd'hui")
    void testGetIsToday() {
      UpcomingEventDTO dto = createUpcomingEventDTO(now());

      assertThat(dto.getIsToday()).isTrue();
    }

    @Test
    @DisplayName("getIsToday - hier")
    void testGetIsTodayHier() {
      UpcomingEventDTO dto = createUpcomingEventDTO(hoursAgo(25));

      assertThat(dto.getIsToday()).isFalse();
    }

    @Test
    @DisplayName("getIsToday - startDate null")
    void testGetIsTodayNull() {
      UpcomingEventDTO dto = new UpcomingEventDTO();

      assertThat(dto.getIsToday()).isFalse();
    }

    @Test
    @DisplayName("getIsTomorrow - demain")
    void testGetIsTomorrow() {
      UpcomingEventDTO dto = createUpcomingEventDTO(daysFromNow(1));

      assertThat(dto.getIsTomorrow()).isTrue();
    }

    @Test
    @DisplayName("getIsTomorrow - aujourd'hui")
    void testGetIsTomorrowAujourdhui() {
      UpcomingEventDTO dto = createUpcomingEventDTO(now());

      assertThat(dto.getIsTomorrow()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests getStatusColor")
  class GetStatusColorTests {

    @ParameterizedTest
    @CsvSource({
        "confirmed, #10B981",
        "open, #3B82F6",
        "cancelled, #EF4444",
        "postponed, #F59E0B",
        "unknown, #6B7280"
    })
    @DisplayName("getStatusColor - tous les statuts")
    void testGetStatusColor(String status, String expectedColor) {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .status(status)
          .build();

      assertThat(dto.getStatusColor()).isEqualTo(expectedColor);
    }

    @Test
    @DisplayName("getStatusColor - status null")
    void testGetStatusColorNull() {
      UpcomingEventDTO dto = new UpcomingEventDTO();

      assertThat(dto.getStatusColor()).isEqualTo("#6B7280");
    }

    @Test
    @DisplayName("getStatusColor - casse insensible")
    void testGetStatusColorCasseInsensible() {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .status("CONFIRMED")
          .build();

      assertThat(dto.getStatusColor()).isEqualTo("#10B981");
    }
  }

  @Nested
  @DisplayName("Tests getStatusLabel")
  class GetStatusLabelTests {

    @ParameterizedTest
    @CsvSource({
        "confirmed, Confirmé",
        "open, Ouvert",
        "cancelled, Annulé",
        "postponed, Reporté",
        "unknown, unknown"
    })
    @DisplayName("getStatusLabel - tous les statuts")
    void testGetStatusLabel(String status, String expectedLabel) {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .status(status)
          .build();

      assertThat(dto.getStatusLabel()).isEqualTo(expectedLabel);
    }

    @Test
    @DisplayName("getStatusLabel - status null")
    void testGetStatusLabelNull() {
      UpcomingEventDTO dto = new UpcomingEventDTO();

      assertThat(dto.getStatusLabel()).isEqualTo("Inconnu");
    }
  }

  @Nested
  @DisplayName("Tests getAvailableSpots")
  class GetAvailableSpotsTests {

    @ParameterizedTest
    @CsvSource({
        "100, 50, 50",
        "100, 100, 0",
        "100, 150, 0",
        "200, 50, 150"
    })
    @DisplayName("getAvailableSpots - calculs variés")
    void testGetAvailableSpots(Integer max, Integer current, Integer expected) {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .maxParticipants(max)
          .currentParticipants(current)
          .build();

      assertThat(dto.getAvailableSpots()).isEqualTo(expected);
    }

    @Test
    @DisplayName("getAvailableSpots - null")
    void testGetAvailableSpotsNull() {
      UpcomingEventDTO dto = new UpcomingEventDTO();

      assertThat(dto.getAvailableSpots()).isEqualTo(0);
    }
  }

  @Nested
  @DisplayName("Tests getParticipationSummary")
  class GetParticipationSummaryTests {

    @Test
    @DisplayName("getParticipationSummary - avec participants")
    void testGetParticipationSummary() {
      UpcomingEventDTO dto = UpcomingEventDTO.builder()
          .maxParticipants(100)
          .currentParticipants(50)
          .build();

      assertThat(dto.getParticipationSummary()).isEqualTo("50/100 participants");
    }

    @Test
    @DisplayName("getParticipationSummary - null")
    void testGetParticipationSummaryNull() {
      UpcomingEventDTO dto = new UpcomingEventDTO();

      assertThat(dto.getParticipationSummary()).isEqualTo("0/0 participants");
    }
  }
}

