package org.raddatz.familienarchiv.document;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.PostgresContainerConfig;
import org.raddatz.familienarchiv.person.Person;
import org.raddatz.familienarchiv.person.PersonRepository;
import org.raddatz.familienarchiv.tag.Tag;
import org.raddatz.familienarchiv.tag.TagRepository;
import org.raddatz.familienarchiv.tag.TagOperator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.transaction.annotation.Transactional;
import software.amazon.awssdk.services.s3.S3Client;

import java.time.LocalDate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

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

/**
 * End-to-end test for the filter-reactive density aggregation.
 * Density bars must recompute as the user changes other filters (sender, tag,
 * status, …). The endpoint deliberately does NOT honour `from`/`to` — the chart
 * is the surface for picking those, so it must always span the broader space
 * the user is selecting within.
 */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ActiveProfiles("test")
@Import(PostgresContainerConfig.class)
@Transactional
class DocumentDensityIntegrationTest {

    @MockitoBean S3Client s3Client;
    @Autowired DocumentService documentService;
    @Autowired DocumentRepository documentRepository;
    @Autowired PersonRepository personRepository;
    @Autowired TagRepository tagRepository;

    private Person hans;
    private Person anna;
    private Tag familieTag;
    private Tag urlaubTag;

    @BeforeEach
    void seed() {
        hans = personRepository.save(Person.builder().firstName("Hans").lastName("Müller").build());
        anna = personRepository.save(Person.builder().firstName("Anna").lastName("Weber").build());
        familieTag = tagRepository.save(Tag.builder().name("Familie").build());
        urlaubTag = tagRepository.save(Tag.builder().name("Urlaub").build());
    }

    private static DensityFilters noFilters() {
        return new DensityFilters(null, null, null, null, null, null, null);
    }

    @Test
    void getDensity_returnsAllMonths_whenNoFiltersApplied() {
        save("a", LocalDate.of(1915, 8, 3), null, Set.of());
        save("b", LocalDate.of(1915, 8, 17), null, Set.of());
        save("c", LocalDate.of(1915, 9, 1), null, Set.of());

        DocumentDensityResult result = documentService.getDensity(noFilters());

        assertThat(result.buckets()).extracting(MonthBucket::month)
                .containsExactly("1915-08", "1915-09");
        assertThat(result.buckets()).extracting(MonthBucket::count).containsExactly(2, 1);
        assertThat(result.minDate()).isEqualTo(LocalDate.of(1915, 8, 3));
        assertThat(result.maxDate()).isEqualTo(LocalDate.of(1915, 9, 1));
    }

    @Test
    void getDensity_filtersBySender() {
        save("a", LocalDate.of(1915, 8, 3), hans, Set.of());
        save("b", LocalDate.of(1916, 1, 4), hans, Set.of());
        save("c", LocalDate.of(1920, 5, 1), anna, Set.of());

        DocumentDensityResult result = documentService.getDensity(
                new DensityFilters(null, hans.getId(), null, null, null, null, null));

        assertThat(result.buckets()).extracting(MonthBucket::month)
                .containsExactly("1915-08", "1916-01");
        assertThat(result.maxDate()).isEqualTo(LocalDate.of(1916, 1, 4));
    }

    @Test
    void getDensity_filtersByTag() {
        save("a", LocalDate.of(1915, 8, 3), null, Set.of(familieTag));
        save("b", LocalDate.of(1920, 5, 1), null, Set.of(urlaubTag));

        DocumentDensityResult result = documentService.getDensity(
                new DensityFilters(null, null, null, List.of("Familie"), null, null, TagOperator.AND));

        assertThat(result.buckets()).extracting(MonthBucket::month).containsExactly("1915-08");
    }

    @Test
    void getDensity_combinesSenderAndTag() {
        save("a", LocalDate.of(1915, 8, 3), hans, Set.of(familieTag));
        save("b", LocalDate.of(1916, 1, 4), hans, Set.of(urlaubTag));
        save("c", LocalDate.of(1920, 5, 1), anna, Set.of(familieTag));

        DocumentDensityResult result = documentService.getDensity(
                new DensityFilters(null, hans.getId(), null, List.of("Familie"), null, null, TagOperator.AND));

        assertThat(result.buckets()).extracting(MonthBucket::month).containsExactly("1915-08");
    }

    @Test
    void getDensity_filtersByStatus() {
        save("a", LocalDate.of(1915, 8, 3), null, Set.of(), DocumentStatus.UPLOADED);
        save("b", LocalDate.of(1916, 1, 4), null, Set.of(), DocumentStatus.PLACEHOLDER);

        DocumentDensityResult result = documentService.getDensity(
                new DensityFilters(null, null, null, null, null, DocumentStatus.UPLOADED, null));

        assertThat(result.buckets()).extracting(MonthBucket::month).containsExactly("1915-08");
    }

    @Test
    void getDensity_returnsEmpty_whenNoDocumentsMatch() {
        save("a", LocalDate.of(1915, 8, 3), hans, Set.of());

        DocumentDensityResult result = documentService.getDensity(
                new DensityFilters(null, anna.getId(), null, null, null, null, null));

        assertThat(result.buckets()).isEmpty();
        assertThat(result.minDate()).isNull();
        assertThat(result.maxDate()).isNull();
    }

    @Test
    void getDensity_excludesDocumentsWithNullDate() {
        save("dated", LocalDate.of(1915, 8, 3), null, Set.of());
        save("undated", null, null, Set.of());

        DocumentDensityResult result = documentService.getDensity(noFilters());

        assertThat(result.buckets()).extracting(MonthBucket::count).containsExactly(1);
    }

    private void save(String suffix, LocalDate date, Person sender, Set<Tag> tags) {
        save(suffix, date, sender, tags, DocumentStatus.UPLOADED);
    }

    private void save(String suffix, LocalDate date, Person sender, Set<Tag> tags, DocumentStatus status) {
        documentRepository.save(Document.builder()
                .title("Doc " + suffix)
                .originalFilename("doc-" + suffix + "-" + UUID.randomUUID() + ".pdf")
                .status(status)
                .documentDate(date)
                .sender(sender)
                .tags(new HashSet<>(tags))
                .build());
    }
}
