package de.fete.adapter.in.web;

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

import de.fete.TestcontainersConfig;
import de.fete.adapter.out.persistence.EventJpaEntity;
import de.fete.adapter.out.persistence.EventJpaRepository;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
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 SpaControllerTest {

  @Autowired
  private MockMvc mockMvc;

  @Autowired
  private EventJpaRepository eventJpaRepository;

  // --- Phase 2: Base functionality ---

  @Test
  void rootServesHtml() throws Exception {
    mockMvc.perform(get("/").accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML));
  }

  @Test
  void rootHtmlDoesNotContainPlaceholder() throws Exception {
    String html = mockMvc.perform(get("/").accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).doesNotContain("<!-- OG_META_TAGS -->");
  }

  @Test
  void createRouteServesHtml() throws Exception {
    mockMvc.perform(get("/create").accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML));
  }

  @Test
  void eventsRouteServesHtml() throws Exception {
    mockMvc.perform(get("/events").accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML));
  }

  // --- Phase 4 (US2): Generic OG meta-tags ---

  @Test
  void rootContainsGenericOgTitle() throws Exception {
    String html = mockMvc.perform(get("/").accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("og:title");
    assertThat(html).contains("content=\"fete\"");
  }

  @Test
  void createRouteContainsGenericOgDescription() throws Exception {
    String html = mockMvc.perform(get("/create").accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("og:description");
    assertThat(html).contains("Privacy-focused event planning");
  }

  @Test
  void unknownRouteReturns404() throws Exception {
    mockMvc.perform(get("/unknown/path").accept(MediaType.TEXT_HTML))
        .andExpect(status().isNotFound());
  }

  // --- Phase 5 (US3): Twitter Card meta-tags ---

  @Test
  void eventRouteContainsTwitterCardTags() throws Exception {
    EventJpaEntity event = seedEvent(
        "Twitter Test", "Testing cards",
        "Europe/Berlin", "Berlin", LocalDate.now().plusDays(30));

    String html = mockMvc.perform(
            get("/events/" + event.getEventToken()).accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("twitter:card");
    assertThat(html).contains("twitter:title");
    assertThat(html).contains("twitter:description");
  }

  @Test
  void genericRouteContainsTwitterCardTags() throws Exception {
    String html = mockMvc.perform(get("/").accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("twitter:card");
    assertThat(html).contains("content=\"summary\"");
  }

  // --- Phase 3 (US1): Event-specific OG meta-tags ---

  @Test
  void eventRouteContainsEventSpecificOgTitle() throws Exception {
    EventJpaEntity event = seedEvent(
        "Birthday Party", "Come celebrate!",
        "Europe/Berlin", "Berlin", LocalDate.now().plusDays(30));

    String html = mockMvc.perform(
            get("/events/" + event.getEventToken()).accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("og:title");
    assertThat(html).contains("Birthday Party");
  }

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

    String html = mockMvc.perform(
            get("/events/" + event.getEventToken()).accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("og:description");
    assertThat(html).contains("Central Park");
    assertThat(html).contains("Bring drinks!");
  }

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

    String html = mockMvc.perform(
            get("/events/" + event.getEventToken()).accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("og:url");
    assertThat(html).contains("/events/" + event.getEventToken());
  }

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

    String html = mockMvc.perform(
            get("/events/" + event.getEventToken()).accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("og:image");
    assertThat(html).contains("/og-image.png");
  }

  @Test
  void unknownEventTokenFallsBackToGenericMeta() throws Exception {
    String html = mockMvc.perform(
            get("/events/" + UUID.randomUUID()).accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("og:title");
    assertThat(html).contains("content=\"fete\"");
  }

  // --- HTML escaping ---

  @Test
  void specialCharactersAreHtmlEscaped() throws Exception {
    EventJpaEntity event = seedEvent(
        "Tom & Jerry's \"Party\"", "Fun <times> & more",
        "Europe/Berlin", "O'Brien's Pub", LocalDate.now().plusDays(30));

    String html = mockMvc.perform(
            get("/events/" + event.getEventToken()).accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("Tom &amp; Jerry");
    assertThat(html).contains("&amp; more");
    assertThat(html).contains("&lt;times&gt;");
    assertThat(html).doesNotContain("content=\"Tom & Jerry");
  }

  // --- Title truncation ---

  @Test
  void longTitleIsTruncatedTo70Chars() throws Exception {
    String longTitle = "A".repeat(80);
    EventJpaEntity event = seedEvent(
        longTitle, "Desc",
        "Europe/Berlin", null, LocalDate.now().plusDays(30));

    String html = mockMvc.perform(
            get("/events/" + event.getEventToken()).accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("A".repeat(67) + "...");
    assertThat(html).doesNotContain("A".repeat(68));
  }

  // --- Description formatting ---

  @Test
  void eventWithoutLocationOmitsPinEmoji() throws Exception {
    EventJpaEntity event = seedEvent(
        "Online Meetup", "Virtual gathering",
        "Europe/Berlin", null, LocalDate.now().plusDays(30));

    String html = mockMvc.perform(
            get("/events/" + event.getEventToken()).accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).doesNotContain("📍");
  }

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

    String html = mockMvc.perform(
            get("/events/" + event.getEventToken()).accept(MediaType.TEXT_HTML))
        .andExpect(status().isOk())
        .andReturn().getResponse().getContentAsString();

    assertThat(html).contains("📅");
    assertThat(html).contains("Berlin");
    assertThat(html).doesNotContain(" — ");
  }

  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 eventJpaRepository.save(entity);
  }
}
