package org.raddatz.familienarchiv.document.transcription;

import jakarta.persistence.EntityManager;
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.annotation.AnnotationRepository;
import org.raddatz.familienarchiv.document.annotation.DocumentAnnotation;
import org.raddatz.familienarchiv.document.Document;
import org.raddatz.familienarchiv.document.DocumentRepository;
import org.raddatz.familienarchiv.document.DocumentStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

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

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

    @Autowired TranscriptionBlockRepository blockRepository;
    @Autowired TranscriptionBlockVersionRepository versionRepository;
    @Autowired DocumentRepository documentRepository;
    @Autowired AnnotationRepository annotationRepository;
    @Autowired EntityManager em;

    private UUID documentId;
    private UUID annotationId;

    @BeforeEach
    void setUp() {
        Document doc = documentRepository.save(Document.builder()
                .title("Testbrief")
                .originalFilename("brief.pdf")
                .status(DocumentStatus.UPLOADED)
                .build());
        documentId = doc.getId();

        DocumentAnnotation annotation = annotationRepository.save(DocumentAnnotation.builder()
                .documentId(documentId)
                .pageNumber(1)
                .x(0.1).y(0.2).width(0.3).height(0.4)
                .color("#00C7B1")
                .build());
        annotationId = annotation.getId();
    }

    // ─── findByDocumentIdOrderBySortOrderAsc ─────────────────────────────────

    @Test
    void findByDocumentIdOrderBySortOrderAsc_returnsBlocksInSortOrder() {
        blockRepository.save(block("Block B", 1));
        blockRepository.save(block("Block A", 0));
        blockRepository.save(block("Block C", 2));

        List<TranscriptionBlock> result = blockRepository.findByDocumentIdOrderBySortOrderAsc(documentId);

        assertThat(result).hasSize(3);
        assertThat(result.get(0).getText()).isEqualTo("Block A");
        assertThat(result.get(1).getText()).isEqualTo("Block B");
        assertThat(result.get(2).getText()).isEqualTo("Block C");
    }

    @Test
    void findByDocumentIdOrderBySortOrderAsc_returnsEmptyList_whenNoBlocksForDocument() {
        UUID otherId = UUID.randomUUID();
        List<TranscriptionBlock> result = blockRepository.findByDocumentIdOrderBySortOrderAsc(otherId);
        assertThat(result).isEmpty();
    }

    @Test
    void findByDocumentIdOrderBySortOrderAsc_doesNotReturnBlocksFromOtherDocument() {
        blockRepository.save(block("My block", 0));

        Document other = documentRepository.save(Document.builder()
                .title("Anderer Brief").originalFilename("other.pdf").status(DocumentStatus.PLACEHOLDER).build());

        List<TranscriptionBlock> result = blockRepository.findByDocumentIdOrderBySortOrderAsc(other.getId());
        assertThat(result).isEmpty();
    }

    // ─── findByIdAndDocumentId ────────────────────────────────────────────────

    @Test
    void findByIdAndDocumentId_returnsBlock_whenBothMatch() {
        TranscriptionBlock saved = blockRepository.save(block("Liebe Tante,", 0));

        Optional<TranscriptionBlock> found = blockRepository.findByIdAndDocumentId(saved.getId(), documentId);

        assertThat(found).isPresent();
        assertThat(found.get().getText()).isEqualTo("Liebe Tante,");
    }

    @Test
    void findByIdAndDocumentId_returnsEmpty_whenDocumentIdDoesNotMatch() {
        TranscriptionBlock saved = blockRepository.save(block("Liebe Tante,", 0));

        Optional<TranscriptionBlock> found = blockRepository.findByIdAndDocumentId(saved.getId(), UUID.randomUUID());

        assertThat(found).isEmpty();
    }

    @Test
    void findByIdAndDocumentId_returnsEmpty_whenBlockIdDoesNotExist() {
        Optional<TranscriptionBlock> found = blockRepository.findByIdAndDocumentId(UUID.randomUUID(), documentId);
        assertThat(found).isEmpty();
    }

    // ─── countByDocumentId ────────────────────────────────────────────────────

    @Test
    void countByDocumentId_returnsZero_whenNoBlocksExist() {
        assertThat(blockRepository.countByDocumentId(documentId)).isZero();
    }

    @Test
    void countByDocumentId_returnsCorrectCount_afterMultipleSaves() {
        blockRepository.save(block("Block 1", 0));
        blockRepository.save(block("Block 2", 1));
        blockRepository.save(block("Block 3", 2));

        assertThat(blockRepository.countByDocumentId(documentId)).isEqualTo(3);
    }

    @Test
    void countByDocumentId_doesNotCountBlocksFromOtherDocument() {
        blockRepository.save(block("Block 1", 0));

        UUID otherId = UUID.randomUUID();
        assertThat(blockRepository.countByDocumentId(otherId)).isZero();
    }

    // ─── version (optimistic lock) ────────────────────────────────────────────

    @Test
    void version_startsAtZero_andIncrementsOnEachSave() {
        TranscriptionBlock saved = blockRepository.saveAndFlush(block("initial", 0));
        assertThat(saved.getVersion()).isZero();

        saved.setText("updated");
        TranscriptionBlock updated = blockRepository.saveAndFlush(saved);
        assertThat(updated.getVersion()).isEqualTo(1);
    }

    // ─── cascade: deleting a block cascades to its versions ──────────────────

    @Test
    @Transactional
    void delete_cascadesToVersions() {
        TranscriptionBlock block = blockRepository.saveAndFlush(block("text", 0));
        versionRepository.saveAndFlush(TranscriptionBlockVersion.builder()
                .blockId(block.getId()).text("text").build());

        assertThat(versionRepository.findByBlockIdOrderByChangedAtDesc(block.getId())).hasSize(1);

        blockRepository.delete(block);
        blockRepository.flush();
        em.clear();

        assertThat(versionRepository.findByBlockIdOrderByChangedAtDesc(block.getId())).isEmpty();
    }

    // ─── cascade: deleting a document cascades to its blocks ─────────────────

    @Test
    @Transactional
    void deleteDocument_cascadesToBlocks() {
        blockRepository.saveAndFlush(block("text", 0));
        assertThat(blockRepository.findByDocumentIdOrderBySortOrderAsc(documentId)).hasSize(1);

        documentRepository.deleteById(documentId);
        documentRepository.flush();
        em.clear();

        assertThat(blockRepository.findByDocumentIdOrderBySortOrderAsc(documentId)).isEmpty();
    }

    // ─── helper ──────────────────────────────────────────────────────────────

    private TranscriptionBlock block(String text, int sortOrder) {
        return TranscriptionBlock.builder()
                .annotationId(annotationId)
                .documentId(documentId)
                .text(text)
                .sortOrder(sortOrder)
                .build();
    }
}
