package org.raddatz.familienarchiv.geschichte;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.PostgresContainerConfig;
import org.raddatz.familienarchiv.geschichte.GeschichteUpdateDTO;
import org.raddatz.familienarchiv.user.AppUser;
import org.raddatz.familienarchiv.geschichte.Geschichte;
import org.raddatz.familienarchiv.geschichte.GeschichteStatus;
import org.raddatz.familienarchiv.geschichte.GeschichteType;
import org.raddatz.familienarchiv.geschichte.GeschichteView;
import org.raddatz.familienarchiv.person.Person;
import org.raddatz.familienarchiv.user.AppUserRepository;
import org.raddatz.familienarchiv.geschichte.GeschichteRepository;
import org.raddatz.familienarchiv.geschichte.journeyitem.JourneyItemService;
import org.raddatz.familienarchiv.person.PersonRepository;
import org.raddatz.familienarchiv.security.Permission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
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.util.List;
import java.util.UUID;

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

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ActiveProfiles("test")
@Import(PostgresContainerConfig.class)
@Transactional
class GeschichteServiceIntegrationTest {

    @MockitoBean
    S3Client s3Client;

    @Autowired GeschichteService geschichteService;
    @Autowired JourneyItemService journeyItemService;
    @Autowired GeschichteRepository geschichteRepository;
    @Autowired PersonRepository personRepository;
    @Autowired AppUserRepository appUserRepository;

    AppUser writer;
    AppUser reader;

    @BeforeEach
    void seed() {
        writer = appUserRepository.save(AppUser.builder()
                .email("writer-int@test")
                .password("hash")
                .build());
        reader = appUserRepository.save(AppUser.builder()
                .email("reader-int@test")
                .password("hash")
                .build());
    }

    @AfterEach
    void clear() {
        SecurityContextHolder.clearContext();
    }

    @Test
    void create_then_publish_then_read_then_delete_full_lifecycle() {
        // Create as writer
        authenticateAs(writer, Permission.BLOG_WRITE);

        Person franz = personRepository.save(Person.builder().firstName("Franz").lastName("Raddatz").build());

        GeschichteUpdateDTO dto = new GeschichteUpdateDTO();
        dto.setTitle("Erinnerung an Opa Franz");
        dto.setBody("<p>Ich erinnere mich, wie er <strong>jeden Sonntag</strong> sang.</p>"
                + "<script>alert('xss')</script>");
        dto.setPersonIds(List.of(franz.getId()));

        GeschichteView created = geschichteService.create(dto);

        assertThat(created.id()).isNotNull();
        assertThat(created.status()).isEqualTo(GeschichteStatus.DRAFT);
        assertThat(created.body())
                .contains("<strong>jeden Sonntag</strong>")
                .doesNotContain("<script>");

        // Reader cannot see DRAFT in list
        authenticateAs(reader, Permission.READ_ALL);
        assertThat(geschichteService.list(null, List.of(), null, 50)).isEmpty();

        // Reader cannot fetch DRAFT by id (404 via GESCHICHTE_NOT_FOUND)
        UUID draftId = created.id();
        org.assertj.core.api.Assertions.assertThatThrownBy(() -> geschichteService.getById(draftId))
                .hasMessageContaining("not found");

        // Publish as writer
        authenticateAs(writer, Permission.BLOG_WRITE);
        GeschichteUpdateDTO publishDto = new GeschichteUpdateDTO();
        publishDto.setStatus(GeschichteStatus.PUBLISHED);
        GeschichteView publishedGesch = geschichteService.update(draftId, publishDto);
        assertThat(publishedGesch.publishedAt()).isNotNull();

        // Reader can now see and fetch it
        authenticateAs(reader, Permission.READ_ALL);
        assertThat(geschichteService.list(null, List.of(), null, 50)).hasSize(1);
        assertThat(geschichteService.list(null, List.of(franz.getId()), null, 50)).hasSize(1);
        Geschichte fetched = geschichteService.getById(draftId);
        GeschichteView fetchedView = geschichteService.toView(fetched, journeyItemService.getItems(draftId));
        assertThat(fetchedView.title()).isEqualTo("Erinnerung an Opa Franz");
        assertThat(fetchedView.persons()).extracting(GeschichteView.PersonView::id).containsExactly(franz.getId());

        // Delete as writer; join rows go with it
        authenticateAs(writer, Permission.BLOG_WRITE);
        geschichteService.delete(draftId);
        assertThat(geschichteRepository.findById(draftId)).isEmpty();

        // The Person itself is untouched (cascade only flows from Geschichte to join table)
        assertThat(personRepository.findById(franz.getId())).isPresent();
    }

    @Test
    void list_filters_with_AND_semantics_when_multiple_personIds_given() {
        // Three published stories, persons overlap so we can prove AND-not-OR:
        //   story_AB:  about A and B
        //   story_AC:  about A and C
        //   story_A:   about A only
        authenticateAs(writer, Permission.BLOG_WRITE);

        Person a = personRepository.save(Person.builder().firstName("Anna").lastName("A").build());
        Person b = personRepository.save(Person.builder().firstName("Bertha").lastName("B").build());
        Person c = personRepository.save(Person.builder().firstName("Carl").lastName("C").build());

        UUID storyAB = publishedStoryWithPersons("Anna & Bertha", List.of(a.getId(), b.getId()));
        UUID storyAC = publishedStoryWithPersons("Anna & Carl", List.of(a.getId(), c.getId()));
        UUID storyA = publishedStoryWithPersons("Anna alone", List.of(a.getId()));

        authenticateAs(reader, Permission.READ_ALL);

        // No filter → all three
        assertThat(geschichteService.list(null, List.of(), null, 50))
                .extracting(GeschichteSummary::getId)
                .containsExactlyInAnyOrder(storyAB, storyAC, storyA);

        // Single filter (Anna) → all three
        assertThat(geschichteService.list(null, List.of(a.getId()), null, 50))
                .extracting(GeschichteSummary::getId)
                .containsExactlyInAnyOrder(storyAB, storyAC, storyA);

        // AND: Anna AND Bertha → only the AB story (NOT story_A, NOT story_AC)
        assertThat(geschichteService.list(null, List.of(a.getId(), b.getId()), null, 50))
                .extracting(GeschichteSummary::getId)
                .containsExactly(storyAB);

        // AND: Bertha AND Carl → none (no story has both)
        assertThat(geschichteService.list(null, List.of(b.getId(), c.getId()), null, 50))
                .isEmpty();

        // AND: Anna AND Bertha AND Carl → none
        assertThat(geschichteService.list(null, List.of(a.getId(), b.getId(), c.getId()), null, 50))
                .isEmpty();
    }

    @Test
    void list_DRAFT_does_not_return_other_users_drafts() {
        // writer creates a draft; writer2 (also BLOG_WRITE) should not see it
        AppUser writer2 = appUserRepository.save(AppUser.builder()
                .email("writer2-int@test")
                .password("hash")
                .build());

        authenticateAs(writer, Permission.BLOG_WRITE);
        GeschichteUpdateDTO dto = new GeschichteUpdateDTO();
        dto.setTitle("Writer 1 draft");
        dto.setBody("<p>private</p>");
        geschichteService.create(dto);

        authenticateAs(writer2, Permission.BLOG_WRITE);
        List<GeschichteSummary> result = geschichteService.list(GeschichteStatus.DRAFT, List.of(), null, 50);

        assertThat(result).isEmpty();
    }

    private UUID publishedStoryWithPersons(String title, List<UUID> personIds) {
        GeschichteUpdateDTO dto = new GeschichteUpdateDTO();
        dto.setTitle(title);
        dto.setBody("<p>body</p>");
        dto.setPersonIds(personIds);
        dto.setStatus(GeschichteStatus.PUBLISHED);
        return geschichteService.create(dto).id();
    }

    private void authenticateAs(AppUser user, Permission... permissions) {
        var authorities = java.util.Arrays.stream(permissions)
                .map(p -> new SimpleGrantedAuthority(p.name()))
                .toList();
        SecurityContextHolder.getContext().setAuthentication(
                new UsernamePasswordAuthenticationToken(user.getEmail(), null, authorities));
    }
}
