package de.fete.adapter.in.web;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.databind.ObjectMapper;
import de.fete.TestcontainersConfig;
import de.fete.adapter.in.web.model.CreateEventRequest;
import de.fete.adapter.in.web.model.CreateEventResponse;
import de.fete.adapter.in.web.model.CreateRsvpRequest;
import de.fete.adapter.in.web.model.CreateRsvpResponse;
import de.fete.adapter.out.persistence.EventJpaEntity;
import de.fete.adapter.out.persistence.EventJpaRepository;
import de.fete.adapter.out.persistence.RsvpJpaEntity;
import de.fete.adapter.out.persistence.RsvpJpaRepository;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfig.class)
class EventControllerIntegrationTest {

  @Autowired
  private MockMvc mockMvc;

  @Autowired
  private ObjectMapper objectMapper;

  @Autowired
  private EventJpaRepository jpaRepository;

  @Autowired
  private RsvpJpaRepository rsvpJpaRepository;

  // --- Create Event tests ---

  @Test
  void createEventWithValidBody() throws Exception {
    var request = new CreateEventRequest()
        .title("Birthday Party")
        .description("Come celebrate!")
        .dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
        .timezone("Europe/Berlin")
        .location("Berlin");

    var result = mockMvc.perform(post("/api/events")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isCreated())
        .andExpect(jsonPath("$.eventToken").isNotEmpty())
        .andExpect(jsonPath("$.organizerToken").isNotEmpty())
        .andExpect(jsonPath("$.title").value("Birthday Party"))
        .andExpect(jsonPath("$.timezone").value("Europe/Berlin"))
        .andExpect(jsonPath("$.dateTime").isNotEmpty())
        .andReturn();

    var response = objectMapper.readValue(
        result.getResponse().getContentAsString(), CreateEventResponse.class);

    EventJpaEntity persisted = jpaRepository
        .findByEventToken(response.getEventToken()).orElseThrow();
    assertThat(persisted.getTitle()).isEqualTo("Birthday Party");
    assertThat(persisted.getDescription()).isEqualTo("Come celebrate!");
    assertThat(persisted.getTimezone()).isEqualTo("Europe/Berlin");
    assertThat(persisted.getLocation()).isEqualTo("Berlin");
    assertThat(persisted.getExpiryDate()).isEqualTo(LocalDate.of(2026, 6, 22));
    assertThat(persisted.getDateTime().toInstant())
        .isEqualTo(request.getDateTime().toInstant());
    assertThat(persisted.getOrganizerToken()).isNotNull();
    assertThat(persisted.getCreatedAt()).isNotNull();
  }

  @Test
  void createEventWithOptionalFieldsNull() throws Exception {
    var request = new CreateEventRequest()
        .title("Minimal Event")
        .dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
        .timezone("UTC");

    var result = mockMvc.perform(post("/api/events")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isCreated())
        .andExpect(jsonPath("$.eventToken").isNotEmpty())
        .andExpect(jsonPath("$.organizerToken").isNotEmpty())
        .andExpect(jsonPath("$.title").value("Minimal Event"))
        .andReturn();

    var response = objectMapper.readValue(
        result.getResponse().getContentAsString(), CreateEventResponse.class);

    EventJpaEntity persisted = jpaRepository
        .findByEventToken(response.getEventToken()).orElseThrow();
    assertThat(persisted.getTitle()).isEqualTo("Minimal Event");
    assertThat(persisted.getDescription()).isNull();
    assertThat(persisted.getLocation()).isNull();
  }

  @Test
  void createEventMissingTitleReturns400() throws Exception {
    long countBefore = jpaRepository.count();

    var request = new CreateEventRequest()
        .dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
        .timezone("Europe/Berlin");

    mockMvc.perform(post("/api/events")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isBadRequest())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"))
        .andExpect(jsonPath("$.title").value("Validation Failed"))
        .andExpect(jsonPath("$.fieldErrors").isArray());

    assertThat(jpaRepository.count()).isEqualTo(countBefore);
  }

  @Test
  void createEventMissingDateTimeReturns400() throws Exception {
    long countBefore = jpaRepository.count();

    var request = new CreateEventRequest()
        .title("No Date")
        .timezone("Europe/Berlin");

    mockMvc.perform(post("/api/events")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isBadRequest())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"))
        .andExpect(jsonPath("$.fieldErrors").isArray());

    assertThat(jpaRepository.count()).isEqualTo(countBefore);
  }

  @Test
  void errorResponseContentTypeIsProblemJson() throws Exception {
    var request = new CreateEventRequest()
        .title("")
        .dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
        .timezone("Europe/Berlin");

    mockMvc.perform(post("/api/events")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isBadRequest())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"));
  }

  @Test
  void createEventWithInvalidTimezoneReturns400() throws Exception {
    long countBefore = jpaRepository.count();

    var request = new CreateEventRequest()
        .title("Bad TZ")
        .dateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)))
        .timezone("Not/A/Zone");

    mockMvc.perform(post("/api/events")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isBadRequest())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"))
        .andExpect(jsonPath("$.type").value("urn:problem-type:invalid-timezone"));

    assertThat(jpaRepository.count()).isEqualTo(countBefore);
  }

  // --- GET /events/{token} tests ---

  @Test
  void getEventReturnsFullResponse() throws Exception {
    EventJpaEntity entity = seedEvent(
        "Summer BBQ", "Bring drinks!", "Europe/Berlin",
        "Central Park", LocalDate.now().plusDays(30));

    mockMvc.perform(get("/api/events/" + entity.getEventToken()))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.eventToken").value(entity.getEventToken().toString()))
        .andExpect(jsonPath("$.title").value("Summer BBQ"))
        .andExpect(jsonPath("$.description").value("Bring drinks!"))
        .andExpect(jsonPath("$.timezone").value("Europe/Berlin"))
        .andExpect(jsonPath("$.location").value("Central Park"))
        .andExpect(jsonPath("$.attendeeCount").value(0))
        .andExpect(jsonPath("$.dateTime").isNotEmpty());
  }

  @Test
  void getEventWithOptionalFieldsAbsent() throws Exception {
    EventJpaEntity entity = seedEvent(
        "Minimal", null, "UTC", null, LocalDate.now().plusDays(30));

    mockMvc.perform(get("/api/events/" + entity.getEventToken()))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.title").value("Minimal"))
        .andExpect(jsonPath("$.description").doesNotExist())
        .andExpect(jsonPath("$.location").doesNotExist())
        .andExpect(jsonPath("$.attendeeCount").value(0));
  }

  @Test
  void getEventNotFoundReturns404() throws Exception {
    mockMvc.perform(get("/api/events/" + UUID.randomUUID()))
        .andExpect(status().isNotFound())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"))
        .andExpect(jsonPath("$.type").value("urn:problem-type:event-not-found"));
  }

  // --- RSVP tests ---

  @Test
  void createRsvpReturns201WithToken() throws Exception {
    EventJpaEntity event = seedEvent(
        "RSVP Event", "Join us!", "Europe/Berlin",
        "Berlin", LocalDate.now().plusDays(30));

    var request = new CreateRsvpRequest().name("Max Mustermann");

    var result = mockMvc.perform(post("/api/events/" + event.getEventToken() + "/rsvps")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isCreated())
        .andExpect(jsonPath("$.rsvpToken").isNotEmpty())
        .andExpect(jsonPath("$.name").value("Max Mustermann"))
        .andReturn();

    var response = objectMapper.readValue(
        result.getResponse().getContentAsString(), CreateRsvpResponse.class);

    RsvpJpaEntity persisted = rsvpJpaRepository
        .findByRsvpToken(response.getRsvpToken()).orElseThrow();
    assertThat(persisted.getName()).isEqualTo("Max Mustermann");
    assertThat(persisted.getEventId()).isEqualTo(event.getId());
  }

  @Test
  void createRsvpWithBlankNameReturns400() throws Exception {
    EventJpaEntity event = seedEvent(
        "RSVP Event", null, "Europe/Berlin",
        null, LocalDate.now().plusDays(30));
    long countBefore = rsvpJpaRepository.count();

    var request = new CreateRsvpRequest().name("");

    mockMvc.perform(post("/api/events/" + event.getEventToken() + "/rsvps")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isBadRequest())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"));

    assertThat(rsvpJpaRepository.count()).isEqualTo(countBefore);
  }

  @Test
  void attendeeCountIncreasesAfterRsvp() throws Exception {
    EventJpaEntity event = seedEvent(
        "Count Event", null, "Europe/Berlin",
        null, LocalDate.now().plusDays(30));

    mockMvc.perform(get("/api/events/" + event.getEventToken()))
        .andExpect(jsonPath("$.attendeeCount").value(0));

    var request = new CreateRsvpRequest().name("First Guest");

    mockMvc.perform(post("/api/events/" + event.getEventToken() + "/rsvps")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isCreated());

    mockMvc.perform(get("/api/events/" + event.getEventToken()))
        .andExpect(jsonPath("$.attendeeCount").value(1));
  }

  @Test
  void createRsvpForUnknownEventReturns404() throws Exception {
    long countBefore = rsvpJpaRepository.count();

    var request = new CreateRsvpRequest().name("Ghost");

    mockMvc.perform(post("/api/events/" + UUID.randomUUID() + "/rsvps")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isNotFound())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"))
        .andExpect(jsonPath("$.type").value("urn:problem-type:event-not-found"));

    assertThat(rsvpJpaRepository.count()).isEqualTo(countBefore);
  }

  @Test
  void createRsvpForExpiredEventReturns409() throws Exception {
    EventJpaEntity event = seedEvent(
        "Expired Party", null, "Europe/Berlin",
        null, LocalDate.now().minusDays(1));
    long countBefore = rsvpJpaRepository.count();

    var request = new CreateRsvpRequest().name("Late Guest");

    mockMvc.perform(post("/api/events/" + event.getEventToken() + "/rsvps")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isConflict())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"))
        .andExpect(jsonPath("$.type").value("urn:problem-type:event-expired"));

    assertThat(rsvpJpaRepository.count()).isEqualTo(countBefore);
  }

  // --- GET /events/{token}/attendees tests ---

  @Test
  void getAttendeesReturnsNamesForOrganizer() throws Exception {
    EventJpaEntity event = seedEvent(
        "Party", null, "Europe/Berlin", null,
        LocalDate.now().plusDays(30));
    seedRsvp(event, "Alice");
    seedRsvp(event, "Bob");

    mockMvc.perform(get("/api/events/" + event.getEventToken()
            + "/attendees?organizerToken=" + event.getOrganizerToken()))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.attendees").isArray())
        .andExpect(jsonPath("$.attendees.length()").value(2))
        .andExpect(jsonPath("$.attendees[0].name").value("Alice"))
        .andExpect(jsonPath("$.attendees[1].name").value("Bob"));
  }

  @Test
  void getAttendeesReturnsEmptyListWhenNoRsvps() throws Exception {
    EventJpaEntity event = seedEvent(
        "Empty Party", null, "Europe/Berlin", null,
        LocalDate.now().plusDays(30));

    mockMvc.perform(get("/api/events/" + event.getEventToken()
            + "/attendees?organizerToken=" + event.getOrganizerToken()))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.attendees").isArray())
        .andExpect(jsonPath("$.attendees.length()").value(0));
  }

  @Test
  void getAttendeesReturns403ForInvalidOrganizerToken() throws Exception {
    EventJpaEntity event = seedEvent(
        "Secret Party", null, "Europe/Berlin", null,
        LocalDate.now().plusDays(30));

    mockMvc.perform(get("/api/events/" + event.getEventToken()
            + "/attendees?organizerToken=" + UUID.randomUUID()))
        .andExpect(status().isForbidden())
        .andExpect(content().contentTypeCompatibleWith(
            "application/problem+json"));
  }

  @Test
  void getAttendeesReturns404ForUnknownEvent() throws Exception {
    mockMvc.perform(get("/api/events/" + UUID.randomUUID()
            + "/attendees?organizerToken=" + UUID.randomUUID()))
        .andExpect(status().isNotFound())
        .andExpect(content().contentTypeCompatibleWith(
            "application/problem+json"));
  }

  // --- Cancel RSVP tests ---

  @Test
  void cancelRsvpReturns204AndDeletesRow() throws Exception {
    EventJpaEntity event = seedEvent(
        "Cancel Event", null, "Europe/Berlin",
        null, LocalDate.now().plusDays(30));
    UUID rsvpToken = seedRsvpAndGetToken(event, "Departing Guest");

    long countBefore = rsvpJpaRepository.count();

    mockMvc.perform(delete("/api/events/" + event.getEventToken()
            + "/rsvps/" + rsvpToken))
        .andExpect(status().isNoContent());

    assertThat(rsvpJpaRepository.count()).isEqualTo(countBefore - 1);
    assertThat(rsvpJpaRepository.findByRsvpToken(rsvpToken)).isEmpty();
  }

  @Test
  void cancelRsvpReturns204WhenAlreadyDeleted() throws Exception {
    EventJpaEntity event = seedEvent(
        "Idempotent Event", null, "Europe/Berlin",
        null, LocalDate.now().plusDays(30));

    mockMvc.perform(delete("/api/events/" + event.getEventToken()
            + "/rsvps/" + UUID.randomUUID()))
        .andExpect(status().isNoContent());
  }

  @Test
  void cancelRsvpReturns204WhenEventNotFound() throws Exception {
    mockMvc.perform(delete("/api/events/" + UUID.randomUUID()
            + "/rsvps/" + UUID.randomUUID()))
        .andExpect(status().isNoContent());
  }

  @Test
  void attendeeCountDecreasesAfterCancelRsvp() throws Exception {
    EventJpaEntity event = seedEvent(
        "Count Cancel Event", null, "Europe/Berlin",
        null, LocalDate.now().plusDays(30));
    UUID rsvpToken = seedRsvpAndGetToken(event, "Leaving Guest");
    seedRsvp(event, "Staying Guest");

    mockMvc.perform(get("/api/events/" + event.getEventToken()))
        .andExpect(jsonPath("$.attendeeCount").value(2));

    mockMvc.perform(delete("/api/events/" + event.getEventToken()
            + "/rsvps/" + rsvpToken))
        .andExpect(status().isNoContent());

    mockMvc.perform(get("/api/events/" + event.getEventToken()))
        .andExpect(jsonPath("$.attendeeCount").value(1));
  }

  // --- Cancel Event tests ---

  @Test
  void cancelEventReturns204AndPersists() throws Exception {
    EventJpaEntity event = seedEvent(
        "Cancel Me", null, "Europe/Berlin", null, LocalDate.now().plusDays(30));

    var body = Map.of(
        "cancelled", true,
        "cancellationReason", "Venue closed");

    mockMvc.perform(patch("/api/events/" + event.getEventToken()
            + "?organizerToken=" + event.getOrganizerToken())
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(body)))
        .andExpect(status().isNoContent());

    EventJpaEntity persisted = jpaRepository
        .findByEventToken(event.getEventToken()).orElseThrow();
    assertThat(persisted.isCancelled()).isTrue();
    assertThat(persisted.getCancellationReason()).isEqualTo("Venue closed");
  }

  @Test
  void cancelEventWithoutReasonReturns204() throws Exception {
    EventJpaEntity event = seedEvent(
        "Cancel No Reason", null, "Europe/Berlin", null, LocalDate.now().plusDays(30));

    var body = Map.of("cancelled", true);

    mockMvc.perform(patch("/api/events/" + event.getEventToken()
            + "?organizerToken=" + event.getOrganizerToken())
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(body)))
        .andExpect(status().isNoContent());

    EventJpaEntity persisted = jpaRepository
        .findByEventToken(event.getEventToken()).orElseThrow();
    assertThat(persisted.isCancelled()).isTrue();
    assertThat(persisted.getCancellationReason()).isNull();
  }

  @Test
  void cancelEventWithWrongOrganizerTokenReturns403() throws Exception {
    EventJpaEntity event = seedEvent(
        "Wrong Token", null, "Europe/Berlin", null, LocalDate.now().plusDays(30));

    var body = Map.of("cancelled", true);

    mockMvc.perform(patch("/api/events/" + event.getEventToken()
            + "?organizerToken=" + UUID.randomUUID())
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(body)))
        .andExpect(status().isForbidden())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"))
        .andExpect(jsonPath("$.type").value("urn:problem-type:invalid-organizer-token"));

    assertThat(jpaRepository.findByEventToken(event.getEventToken())
        .orElseThrow().isCancelled()).isFalse();
  }

  @Test
  void cancelEventNotFoundReturns404() throws Exception {
    var body = Map.of("cancelled", true);

    mockMvc.perform(patch("/api/events/" + UUID.randomUUID()
            + "?organizerToken=" + UUID.randomUUID())
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(body)))
        .andExpect(status().isNotFound())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"))
        .andExpect(jsonPath("$.type").value("urn:problem-type:event-not-found"));
  }

  @Test
  void cancelAlreadyCancelledEventReturns409() throws Exception {
    EventJpaEntity event = seedCancelledEvent("Already Cancelled");

    var body = Map.of("cancelled", true);

    mockMvc.perform(patch("/api/events/" + event.getEventToken()
            + "?organizerToken=" + event.getOrganizerToken())
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(body)))
        .andExpect(status().isConflict())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"))
        .andExpect(jsonPath("$.type").value("urn:problem-type:event-already-cancelled"));
  }

  @Test
  void getEventReturnsCancelledFields() throws Exception {
    EventJpaEntity event = seedCancelledEvent("Weather Event");

    mockMvc.perform(get("/api/events/" + event.getEventToken()))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.cancelled").value(true))
        .andExpect(jsonPath("$.cancellationReason").value("Cancelled"));
  }

  @Test
  void getEventReturnsNotCancelledByDefault() throws Exception {
    EventJpaEntity event = seedEvent(
        "Active Event", null, "Europe/Berlin", null, LocalDate.now().plusDays(30));

    mockMvc.perform(get("/api/events/" + event.getEventToken()))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.cancelled").value(false))
        .andExpect(jsonPath("$.cancellationReason").doesNotExist());
  }

  @Test
  void createRsvpOnCancelledEventReturns409() throws Exception {
    EventJpaEntity event = seedCancelledEvent("Cancelled RSVP");
    long countBefore = rsvpJpaRepository.count();

    var request = new CreateRsvpRequest().name("Late Guest");

    mockMvc.perform(post("/api/events/" + event.getEventToken() + "/rsvps")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isConflict())
        .andExpect(content().contentTypeCompatibleWith("application/problem+json"))
        .andExpect(jsonPath("$.type").value("urn:problem-type:event-cancelled"));

    assertThat(rsvpJpaRepository.count()).isEqualTo(countBefore);
  }

  private EventJpaEntity seedCancelledEvent(String title) {
    var entity = new EventJpaEntity();
    entity.setEventToken(UUID.randomUUID());
    entity.setOrganizerToken(UUID.randomUUID());
    entity.setTitle(title);
    entity.setDateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)));
    entity.setTimezone("Europe/Berlin");
    entity.setExpiryDate(LocalDate.now().plusDays(30));
    entity.setCreatedAt(OffsetDateTime.now());
    entity.setCancelled(true);
    entity.setCancellationReason("Cancelled");
    return jpaRepository.save(entity);
  }

  private UUID seedRsvpAndGetToken(EventJpaEntity event, String name) {
    var rsvp = new RsvpJpaEntity();
    UUID token = UUID.randomUUID();
    rsvp.setRsvpToken(token);
    rsvp.setEventId(event.getId());
    rsvp.setName(name);
    rsvpJpaRepository.save(rsvp);
    return token;
  }

  private void seedRsvp(EventJpaEntity event, String name) {
    var rsvp = new RsvpJpaEntity();
    rsvp.setRsvpToken(UUID.randomUUID());
    rsvp.setEventId(event.getId());
    rsvp.setName(name);
    rsvpJpaRepository.save(rsvp);
  }

  private EventJpaEntity seedEvent(
      String title, String description, String timezone,
      String location, LocalDate expiryDate) {
    var entity = new EventJpaEntity();
    entity.setEventToken(UUID.randomUUID());
    entity.setOrganizerToken(UUID.randomUUID());
    entity.setTitle(title);
    entity.setDescription(description);
    entity.setDateTime(OffsetDateTime.of(2026, 6, 15, 20, 0, 0, 0, ZoneOffset.ofHours(2)));
    entity.setTimezone(timezone);
    entity.setLocation(location);
    entity.setExpiryDate(expiryDate);
    entity.setCreatedAt(OffsetDateTime.now());
    return jpaRepository.save(entity);
  }
}
