package de.fete.application.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import de.fete.domain.model.CreateEventCommand;
import de.fete.domain.model.Event;
import de.fete.domain.model.EventToken;
import de.fete.domain.port.out.EventRepository;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class EventServiceTest {

  private static final ZoneId ZONE = ZoneId.of("Europe/Berlin");
  private static final Instant FIXED_INSTANT =
      LocalDate.of(2026, 3, 5).atStartOfDay(ZONE).toInstant();
  private static final Clock FIXED_CLOCK = Clock.fixed(FIXED_INSTANT, ZONE);
  private static final LocalDate TODAY = LocalDate.ofInstant(FIXED_INSTANT, ZONE);

  @Mock
  private EventRepository eventRepository;

  private EventService eventService;

  @BeforeEach
  void setUp() {
    eventService = new EventService(eventRepository, FIXED_CLOCK);
  }

  @Test
  void createEventWithValidCommand() {
    when(eventRepository.save(any(Event.class)))
        .thenAnswer(invocation -> invocation.getArgument(0));

    var command = new CreateEventCommand(
        "Birthday Party",
        "Come celebrate!",
        TODAY.plusDays(90).atStartOfDay(ZONE).toOffsetDateTime(),
        ZONE,
        "Berlin"
    );

    Event result = eventService.createEvent(command);

    assertThat(result.title()).isEqualTo("Birthday Party");
    assertThat(result.description()).isEqualTo("Come celebrate!");
    assertThat(result.timezone()).isEqualTo(ZONE);
    assertThat(result.location()).isEqualTo("Berlin");
    assertThat(result.eventToken()).isNotNull();
    assertThat(result.organizerToken()).isNotNull();
    assertThat(result.createdAt()).isEqualTo(OffsetDateTime.ofInstant(FIXED_INSTANT, ZONE));
  }

  @Test
  void repositorySaveCalledExactlyOnce() {
    when(eventRepository.save(any(Event.class)))
        .thenAnswer(invocation -> invocation.getArgument(0));

    var command = new CreateEventCommand(
        "Test", null,
        TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(), ZONE, null
    );

    eventService.createEvent(command);

    ArgumentCaptor<Event> captor = ArgumentCaptor.forClass(Event.class);
    verify(eventRepository, times(1)).save(captor.capture());
    assertThat(captor.getValue().title()).isEqualTo("Test");
  }

  @Test
  void expiryDateIsEventDatePlusSevenDays() {
    when(eventRepository.save(any(Event.class)))
        .thenAnswer(invocation -> invocation.getArgument(0));

    var eventDate = TODAY.plusDays(10);
    var command = new CreateEventCommand(
        "Test", null,
        eventDate.atStartOfDay(ZONE).toOffsetDateTime(), ZONE, null
    );

    Event result = eventService.createEvent(command);

    assertThat(result.expiryDate()).isEqualTo(eventDate.plusDays(7));
  }

  // --- GetEventUseCase tests (T004) ---

  @Test
  void getByEventTokenReturnsEvent() {
    EventToken token = EventToken.generate();
    var event = new Event(null, token, null, "Found Event", null, null, null, null, null, null,
        false, null);
    when(eventRepository.findByEventToken(token))
        .thenReturn(Optional.of(event));

    Optional<Event> result = eventService.getByEventToken(token);

    assertThat(result).isPresent();
    assertThat(result.get().title()).isEqualTo("Found Event");
  }

  @Test
  void getByEventTokenReturnsEmptyForUnknownToken() {
    EventToken token = EventToken.generate();
    when(eventRepository.findByEventToken(token))
        .thenReturn(Optional.empty());

    Optional<Event> result = eventService.getByEventToken(token);

    assertThat(result).isEmpty();
  }

  // --- Timezone validation tests (T006) ---

  @Test
  void createEventWithValidTimezoneSucceeds() {
    when(eventRepository.save(any(Event.class)))
        .thenAnswer(invocation -> invocation.getArgument(0));

    var command = new CreateEventCommand(
        "Test", null,
        TODAY.plusDays(10).atStartOfDay(ZONE).toOffsetDateTime(),
        ZoneId.of("America/New_York"), null
    );

    Event result = eventService.createEvent(command);

    assertThat(result.timezone()).isEqualTo(ZoneId.of("America/New_York"));
  }
}
