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

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

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

/**
 * Tests unitaires pour DashboardDataResponse.
 * Couvre tous les getters et méthodes utilitaires (toutes branches).
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-02-21
 */
@DisplayName("Tests DashboardDataResponse")
class DashboardDataResponseTest {

  /** Sous-classe pour couvrir la branche getIsToday() == null dans le filtre. */
  private static final class EventWithNullIsToday extends UpcomingEventResponse {
    @Override
    public Boolean getIsToday() { return null; }
  }

  /** Sous-classe pour couvrir la branche getIsTomorrow() == null dans le filtre. */
  private static final class EventWithNullIsTomorrow extends UpcomingEventResponse {
    @Override
    public Boolean getIsTomorrow() { return null; }
  }

  /** Sous-classe pour couvrir la branche getIsRecent() == null dans le filtre. */
  private static final class ActivityWithNullIsRecent extends RecentActivityResponse {
    @Override
    public Boolean getIsRecent() { return null; }
  }

  /** Sous-classe pour couvrir la branche getIsToday() == null (activité) dans le filtre. */
  private static final class ActivityWithNullIsToday extends RecentActivityResponse {
    @Override
    public Boolean getIsToday() { return null; }
  }

  @Nested
  @DisplayName("Constructeur et getters/setters")
  class ConstructeurEtGetters {

    @Test
    @DisplayName("builder et getters fonctionnent")
    void testBuilderEtGetters() {
      DashboardStatsResponse stats = new DashboardStatsResponse();
      List<RecentActivityResponse> activities = List.of();
      List<UpcomingEventResponse> events = List.of();
      Map<String, Object> prefs = Map.of("theme", "dark");
      DashboardDataResponse dto = DashboardDataResponse.builder()
          .stats(stats)
          .recentActivities(activities)
          .upcomingEvents(events)
          .userPreferences(prefs)
          .organizationId("org1")
          .userId("user1")
          .build();
      assertThat(dto.getStats()).isSameAs(stats);
      assertThat(dto.getRecentActivities()).isSameAs(activities);
      assertThat(dto.getUpcomingEvents()).isSameAs(events);
      assertThat(dto.getUserPreferences()).isEqualTo(prefs);
      assertThat(dto.getOrganizationId()).isEqualTo("org1");
      assertThat(dto.getUserId()).isEqualTo("user1");
    }

    @Test
    @DisplayName("no-args et setters")
    void testNoArgsEtSetters() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setStats(new DashboardStatsResponse());
      dto.setOrganizationId("o2");
      assertThat(dto.getOrganizationId()).isEqualTo("o2");
    }
  }

  @Nested
  @DisplayName("getTodayEventsCount")
  class GetTodayEventsCount {

    @Test
    @DisplayName("retourne 0 quand upcomingEvents est null")
    void testNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUpcomingEvents(null);
      assertThat(dto.getTodayEventsCount()).isEqualTo(0);
    }

    @Test
    @DisplayName("compte les événements avec isToday true")
    void testCompteToday() {
      LocalDateTime now = LocalDateTime.now();
      UpcomingEventResponse today = UpcomingEventResponse.builder().startDate(now).build();
      UpcomingEventResponse other = UpcomingEventResponse.builder().startDate(now.plusDays(2)).build();
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUpcomingEvents(List.of(today, other));
      assertThat(dto.getTodayEventsCount()).isEqualTo(1);
    }

    @Test
    @DisplayName("exclut les événements dont getIsToday() est null")
    void testFiltreIsTodayNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUpcomingEvents(List.of(new EventWithNullIsToday()));
      assertThat(dto.getTodayEventsCount()).isEqualTo(0);
    }

    @Test
    @DisplayName("une liste mixte (null, true, false) compte uniquement les isToday true")
    void testFiltreMixteToday() {
      LocalDateTime now = LocalDateTime.now();
      UpcomingEventResponse today = UpcomingEventResponse.builder().startDate(now).build();
      UpcomingEventResponse other = UpcomingEventResponse.builder().startDate(now.plusDays(2)).build();
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUpcomingEvents(List.of(new EventWithNullIsToday(), today, other));
      assertThat(dto.getTodayEventsCount()).isEqualTo(1);
    }
  }

  @Nested
  @DisplayName("getTomorrowEventsCount")
  class GetTomorrowEventsCount {

    @Test
    @DisplayName("retourne 0 quand upcomingEvents est null")
    void testNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUpcomingEvents(null);
      assertThat(dto.getTomorrowEventsCount()).isEqualTo(0);
    }

    @Test
    @DisplayName("compte les événements avec isTomorrow true")
    void testCompteTomorrow() {
      UpcomingEventResponse tomorrow = UpcomingEventResponse.builder()
          .startDate(LocalDateTime.now().plusDays(1)).build();
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUpcomingEvents(List.of(tomorrow));
      assertThat(dto.getTomorrowEventsCount()).isEqualTo(1);
    }

    @Test
    @DisplayName("exclut les événements dont getIsTomorrow() est null")
    void testFiltreIsTomorrowNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUpcomingEvents(List.of(new EventWithNullIsTomorrow()));
      assertThat(dto.getTomorrowEventsCount()).isEqualTo(0);
    }

    @Test
    @DisplayName("une liste mixte pour demain compte uniquement les isTomorrow true")
    void testFiltreMixteTomorrow() {
      UpcomingEventResponse tomorrow = UpcomingEventResponse.builder().startDate(LocalDateTime.now().plusDays(1)).build();
      UpcomingEventResponse other = UpcomingEventResponse.builder().startDate(LocalDateTime.now().plusDays(3)).build();
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUpcomingEvents(List.of(new EventWithNullIsTomorrow(), tomorrow, other));
      assertThat(dto.getTomorrowEventsCount()).isEqualTo(1);
    }
  }

  @Nested
  @DisplayName("getRecentActivitiesCount")
  class GetRecentActivitiesCount {

    @Test
    @DisplayName("retourne 0 quand recentActivities est null")
    void testNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setRecentActivities(null);
      assertThat(dto.getRecentActivitiesCount()).isEqualTo(0);
    }

    @Test
    @DisplayName("compte les activités avec isRecent true")
    void testCompteRecent() {
      RecentActivityResponse recent = RecentActivityResponse.builder()
          .timestamp(LocalDateTime.now().minusHours(1)).build();
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setRecentActivities(List.of(recent));
      assertThat(dto.getRecentActivitiesCount()).isEqualTo(1);
    }

    @Test
    @DisplayName("exclut les activités dont getIsRecent() est null")
    void testFiltreIsRecentNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setRecentActivities(List.of(new ActivityWithNullIsRecent()));
      assertThat(dto.getRecentActivitiesCount()).isEqualTo(0);
    }

    @Test
    @DisplayName("une liste mixte pour isRecent compte uniquement les isRecent true")
    void testFiltreMixteRecent() {
      RecentActivityResponse recent = RecentActivityResponse.builder().timestamp(LocalDateTime.now().minusHours(1)).build();
      RecentActivityResponse old = RecentActivityResponse.builder().timestamp(LocalDateTime.now().minusDays(2)).build();
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setRecentActivities(List.of(new ActivityWithNullIsRecent(), recent, old));
      assertThat(dto.getRecentActivitiesCount()).isEqualTo(1);
    }
  }

  @Nested
  @DisplayName("getTodayActivitiesCount")
  class GetTodayActivitiesCount {

    @Test
    @DisplayName("retourne 0 quand recentActivities est null")
    void testNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setRecentActivities(null);
      assertThat(dto.getTodayActivitiesCount()).isEqualTo(0);
    }

    @Test
    @DisplayName("compte les activités avec isToday true")
    void testCompteToday() {
      RecentActivityResponse today = RecentActivityResponse.builder()
          .timestamp(LocalDateTime.now()).build();
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setRecentActivities(List.of(today));
      assertThat(dto.getTodayActivitiesCount()).isEqualTo(1);
    }

    @Test
    @DisplayName("exclut les activités dont getIsToday() est null")
    void testFiltreIsTodayNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setRecentActivities(List.of(new ActivityWithNullIsToday()));
      assertThat(dto.getTodayActivitiesCount()).isEqualTo(0);
    }

    @Test
    @DisplayName("une liste mixte pour isToday activité compte uniquement les isToday true")
    void testFiltreMixteTodayActivity() {
      RecentActivityResponse today = RecentActivityResponse.builder().timestamp(LocalDateTime.now()).build();
      RecentActivityResponse other = RecentActivityResponse.builder().timestamp(LocalDateTime.now().minusDays(1)).build();
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setRecentActivities(List.of(new ActivityWithNullIsToday(), today, other));
      assertThat(dto.getTodayActivitiesCount()).isEqualTo(1);
    }
  }

  @Nested
  @DisplayName("getHasUpcomingEvents")
  class GetHasUpcomingEvents {

    @Test
    @DisplayName("retourne false quand upcomingEvents est null")
    void testNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUpcomingEvents(null);
      assertThat(dto.getHasUpcomingEvents()).isFalse();
    }

    @Test
    @DisplayName("retourne false quand upcomingEvents est vide")
    void testVide() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUpcomingEvents(List.of());
      assertThat(dto.getHasUpcomingEvents()).isFalse();
    }

    @Test
    @DisplayName("retourne true quand upcomingEvents non vide")
    void testNonVide() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUpcomingEvents(List.of(UpcomingEventResponse.builder().build()));
      assertThat(dto.getHasUpcomingEvents()).isTrue();
    }
  }

  @Nested
  @DisplayName("getHasRecentActivities")
  class GetHasRecentActivities {

    @Test
    @DisplayName("retourne false quand recentActivities est null")
    void testNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setRecentActivities(null);
      assertThat(dto.getHasRecentActivities()).isFalse();
    }

    @Test
    @DisplayName("retourne false quand recentActivities est vide")
    void testVide() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setRecentActivities(List.of());
      assertThat(dto.getHasRecentActivities()).isFalse();
    }

    @Test
    @DisplayName("retourne true quand recentActivities non vide")
    void testNonVide() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setRecentActivities(List.of(RecentActivityResponse.builder().build()));
      assertThat(dto.getHasRecentActivities()).isTrue();
    }
  }

  @Nested
  @DisplayName("getThemePreference")
  class GetThemePreference {

    @Test
    @DisplayName("retourne royal_teal quand userPreferences est null")
    void testNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(null);
      assertThat(dto.getThemePreference()).isEqualTo("royal_teal");
    }

    @Test
    @DisplayName("retourne la valeur theme si présente")
    void testAvecTheme() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(Map.of("theme", "dark"));
      assertThat(dto.getThemePreference()).isEqualTo("dark");
    }

    @Test
    @DisplayName("retourne royal_teal par défaut si theme absent")
    void testDefaut() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(Map.of("other", "x"));
      assertThat(dto.getThemePreference()).isEqualTo("royal_teal");
    }
  }

  @Nested
  @DisplayName("getLanguagePreference")
  class GetLanguagePreference {

    @Test
    @DisplayName("retourne fr quand userPreferences est null")
    void testNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(null);
      assertThat(dto.getLanguagePreference()).isEqualTo("fr");
    }

    @Test
    @DisplayName("retourne la valeur language si présente")
    void testAvecLanguage() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(Map.of("language", "en"));
      assertThat(dto.getLanguagePreference()).isEqualTo("en");
    }

    @Test
    @DisplayName("retourne fr par défaut si language absent")
    void testDefaut() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(Map.of());
      assertThat(dto.getLanguagePreference()).isEqualTo("fr");
    }
  }

  @Nested
  @DisplayName("getNotificationsEnabled")
  class GetNotificationsEnabled {

    @Test
    @DisplayName("retourne true quand userPreferences est null")
    void testNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(null);
      assertThat(dto.getNotificationsEnabled()).isTrue();
    }

    @Test
    @DisplayName("retourne la valeur notifications si présente")
    void testAvecValeur() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(Map.of("notifications", false));
      assertThat(dto.getNotificationsEnabled()).isFalse();
    }

    @Test
    @DisplayName("retourne true par défaut si notifications absent")
    void testDefaut() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(Map.of());
      assertThat(dto.getNotificationsEnabled()).isTrue();
    }
  }

  @Nested
  @DisplayName("getAutoRefreshEnabled")
  class GetAutoRefreshEnabled {

    @Test
    @DisplayName("retourne true quand userPreferences est null")
    void testNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(null);
      assertThat(dto.getAutoRefreshEnabled()).isTrue();
    }

    @Test
    @DisplayName("retourne la valeur autoRefresh si présente")
    void testAvecValeur() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(Map.of("autoRefresh", false));
      assertThat(dto.getAutoRefreshEnabled()).isFalse();
    }
  }

  @Nested
  @DisplayName("getRefreshInterval")
  class GetRefreshInterval {

    @Test
    @DisplayName("retourne 300 quand userPreferences est null")
    void testNull() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(null);
      assertThat(dto.getRefreshInterval()).isEqualTo(300);
    }

    @Test
    @DisplayName("retourne la valeur refreshInterval si présente")
    void testAvecValeur() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(Map.of("refreshInterval", 60));
      assertThat(dto.getRefreshInterval()).isEqualTo(60);
    }

    @Test
    @DisplayName("retourne 300 par défaut si refreshInterval absent")
    void testDefaut() {
      DashboardDataResponse dto = new DashboardDataResponse();
      dto.setUserPreferences(Map.of());
      assertThat(dto.getRefreshInterval()).isEqualTo(300);
    }
  }
}
