package org.raddatz.familienarchiv.geschichte;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.PostgresContainerConfig;
import org.raddatz.familienarchiv.config.FlywayConfig;
import org.raddatz.familienarchiv.document.Document;
import org.raddatz.familienarchiv.document.DocumentRepository;
import org.raddatz.familienarchiv.document.DocumentStatus;
import org.raddatz.familienarchiv.geschichte.journeyitem.JourneyItem;
import org.raddatz.familienarchiv.geschichte.journeyitem.JourneyItemRepository;
import org.raddatz.familienarchiv.person.Person;
import org.raddatz.familienarchiv.person.PersonRepository;
import org.raddatz.familienarchiv.user.AppUser;
import org.raddatz.familienarchiv.user.AppUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import org.springframework.context.annotation.Import;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

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

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import({PostgresContainerConfig.class, FlywayConfig.class})
class GeschichteListProjectionTest {

    @Autowired GeschichteRepository geschichteRepository;
    @Autowired AppUserRepository appUserRepository;
    @Autowired PersonRepository personRepository;
    @Autowired DocumentRepository documentRepository;
    @Autowired JourneyItemRepository journeyItemRepository;

    AppUser author;
    AppUser otherAuthor;

    @BeforeEach
    void setUp() {
        geschichteRepository.deleteAll();
        author = appUserRepository.save(AppUser.builder()
                .email("author@test").password("pw").build());
        otherAuthor = appUserRepository.save(AppUser.builder()
                .email("other@test").password("pw").build());
    }

    // ─── findSummaries returns only the requested status ─────────────────────

    @Test
    void findSummaries_returns_only_published_stories_when_effectiveStatus_is_PUBLISHED() {
        geschichteRepository.save(published("Veröffentlicht", author));
        geschichteRepository.save(draft("Entwurf", author));

        List<GeschichteSummary> result = geschichteRepository.findSummaries(
                GeschichteStatus.PUBLISHED, null, sentinel(), 0, null);

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getTitle()).isEqualTo("Veröffentlicht");
    }

    @Test
    void findSummaries_carries_updatedAt_for_dashboard_relative_times() {
        // ReaderDraftsModule renders "bearbeitet vor X" from updatedAt — the
        // projection must carry it for drafts, where publishedAt is null.
        geschichteRepository.save(draft("Mein Entwurf", author));

        List<GeschichteSummary> result = geschichteRepository.findSummaries(
                GeschichteStatus.DRAFT, author.getId(), sentinel(), 0, null);

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getUpdatedAt()).isNotNull();
    }

    @Test
    void findSummaries_returns_empty_list_when_no_published_geschichten_exist() {
        geschichteRepository.save(draft("Nur Entwurf", author));

        List<GeschichteSummary> result = geschichteRepository.findSummaries(
                GeschichteStatus.PUBLISHED, null, sentinel(), 0, null);

        assertThat(result).isEmpty();
    }

    // ─── AuthorSummary nested projection ─────────────────────────────────────

    @Test
    void findSummaries_exposes_nested_author_names_but_never_email() {
        AppUser richAuthor = appUserRepository.save(AppUser.builder()
                .firstName("Franz").lastName("Raddatz")
                .email("franz@raddatz.de").password("pw").build());
        geschichteRepository.save(published("Briefe aus der Front", richAuthor));

        List<GeschichteSummary> result = geschichteRepository.findSummaries(
                GeschichteStatus.PUBLISHED, null, sentinel(), 0, null);

        assertThat(result).hasSize(1);
        GeschichteSummary.AuthorSummary a = result.get(0).getAuthor();
        assertThat(a.getFirstName()).isEqualTo("Franz");
        assertThat(a.getLastName()).isEqualTo("Raddatz");
        // Design rule (GeschichteView.AuthorView javadoc): author projections never
        // expose email or group memberships to readers.
        assertThat(GeschichteSummary.AuthorSummary.class.getMethods())
                .extracting(java.lang.reflect.Method::getName)
                .doesNotContain("getEmail");
    }

    // ─── GeschichteType is exposed ────────────────────────────────────────────

    @Test
    void findSummaries_exposes_type_field() {
        Geschichte journey = Geschichte.builder()
                .title("Eine Reise")
                .status(GeschichteStatus.PUBLISHED)
                .type(GeschichteType.JOURNEY)
                .author(author)
                .publishedAt(LocalDateTime.now())
                .build();
        geschichteRepository.save(journey);

        List<GeschichteSummary> result = geschichteRepository.findSummaries(
                GeschichteStatus.PUBLISHED, null, sentinel(), 0, null);

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getType()).isEqualTo(GeschichteType.JOURNEY);
    }

    // ─── authorId filter (own-drafts gate) ───────────────────────────────────

    @Test
    void findSummaries_with_authorId_returns_only_own_drafts() {
        geschichteRepository.save(draft("Mein Entwurf", author));
        geschichteRepository.save(draft("Fremder Entwurf", otherAuthor));

        List<GeschichteSummary> result = geschichteRepository.findSummaries(
                GeschichteStatus.DRAFT, author.getId(), sentinel(), 0, null);

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getTitle()).isEqualTo("Mein Entwurf");
    }

    // ─── personCount = 0 → no person filter ──────────────────────────────────

    @Test
    void findSummaries_with_personCount_zero_ignores_personIds_and_returns_all() {
        geschichteRepository.save(published("A", author));
        geschichteRepository.save(published("B", author));

        List<GeschichteSummary> result = geschichteRepository.findSummaries(
                GeschichteStatus.PUBLISHED, null, sentinel(), 0, null);

        assertThat(result).hasSize(2);
    }

    // ─── personCount > 0 AND-semantics ───────────────────────────────────────

    @Test
    void findSummaries_with_one_personId_returns_only_linked_stories() {
        Person franz = personRepository.save(Person.builder().firstName("Franz").lastName("R").build());
        Person anna = personRepository.save(Person.builder().firstName("Anna").lastName("R").build());

        Geschichte withFranz = published("Franz story", author);
        withFranz.getPersons().add(franz);
        geschichteRepository.save(withFranz);

        Geschichte withAnna = published("Anna story", author);
        withAnna.getPersons().add(anna);
        geschichteRepository.save(withAnna);

        List<GeschichteSummary> result = geschichteRepository.findSummaries(
                GeschichteStatus.PUBLISHED, null, List.of(franz.getId()), 1, null);

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getTitle()).isEqualTo("Franz story");
    }

    @Test
    void findSummaries_with_two_personIds_uses_AND_semantics() {
        Person franz = personRepository.save(Person.builder().firstName("Franz").lastName("R").build());
        Person anna = personRepository.save(Person.builder().firstName("Anna").lastName("R").build());

        Geschichte both = published("Both", author);
        both.getPersons().add(franz);
        both.getPersons().add(anna);
        geschichteRepository.save(both);

        Geschichte onlyFranz = published("Only Franz", author);
        onlyFranz.getPersons().add(franz);
        geschichteRepository.save(onlyFranz);

        List<GeschichteSummary> result = geschichteRepository.findSummaries(
                GeschichteStatus.PUBLISHED, null, List.of(franz.getId(), anna.getId()), 2, null);

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getTitle()).isEqualTo("Both");
    }

    // ─── documentId filter (JPQL EXISTS subquery) ────────────────────────────

    @Test
    void findSummaries_with_documentId_returns_journey_containing_that_document() {
        Document doc = documentRepository.save(Document.builder()
                .title("Brief").originalFilename("brief.pdf").status(DocumentStatus.UPLOADED).build());
        Geschichte withDoc = geschichteRepository.save(journey("Reise mit Dokument", author));
        Geschichte withoutDoc = geschichteRepository.save(journey("Reise ohne Dokument", author));
        journeyItemRepository.save(JourneyItem.builder()
                .geschichte(withDoc).document(doc).position(1).build());

        List<GeschichteSummary> result = geschichteRepository.findSummaries(
                GeschichteStatus.PUBLISHED, null, sentinel(), 0, doc.getId());

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getTitle()).isEqualTo("Reise mit Dokument");
        assertThat(result).extracting(GeschichteSummary::getTitle).doesNotContain("Reise ohne Dokument");
    }

    @Test
    void findSummaries_with_unknown_documentId_returns_empty() {
        geschichteRepository.save(journey("Irgendeine Reise", author));

        List<GeschichteSummary> result = geschichteRepository.findSummaries(
                GeschichteStatus.PUBLISHED, null, sentinel(), 0, UUID.randomUUID());

        assertThat(result).isEmpty();
    }

    // ─── helpers ─────────────────────────────────────────────────────────────

    private Geschichte published(String title, AppUser writer) {
        return Geschichte.builder()
                .title(title)
                .status(GeschichteStatus.PUBLISHED)
                .author(writer)
                .publishedAt(LocalDateTime.now())
                .build();
    }

    private Geschichte draft(String title, AppUser writer) {
        return Geschichte.builder()
                .title(title)
                .status(GeschichteStatus.DRAFT)
                .author(writer)
                .build();
    }

    private Geschichte journey(String title, AppUser writer) {
        return Geschichte.builder()
                .title(title)
                .status(GeschichteStatus.PUBLISHED)
                .type(GeschichteType.JOURNEY)
                .author(writer)
                .publishedAt(LocalDateTime.now())
                .build();
    }

    /** Sentinel UUID passed when personCount=0 — the IN() clause is never evaluated. */
    private List<UUID> sentinel() {
        return List.of(UUID.fromString("00000000-0000-0000-0000-000000000000"));
    }
}
