package org.raddatz.familienarchiv.geschichte.journeyitem;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.PostgresContainerConfig;
import org.raddatz.familienarchiv.document.Document;
import org.raddatz.familienarchiv.document.DocumentRepository;
import org.raddatz.familienarchiv.document.DocumentStatus;
import org.raddatz.familienarchiv.geschichte.Geschichte;
import org.raddatz.familienarchiv.geschichte.GeschichteRepository;
import org.raddatz.familienarchiv.geschichte.GeschichteStatus;
import org.raddatz.familienarchiv.geschichte.GeschichteType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import software.amazon.awssdk.services.s3.S3Client;

import java.util.UUID;

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

/**
 * Raw-SQL constraint tests for journey_items — deliberately NOT @Transactional at class level.
 * A DataIntegrityViolationException inside a class-level @Transactional marks the tx
 * rollback-only and cascades into TransactionSystemException on teardown.
 * Each test inserts via jdbcTemplate and uses explicit SQL teardown.
 */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ActiveProfiles("test")
@Import(PostgresContainerConfig.class)
class JourneyItemConstraintsTest {

    @MockitoBean
    S3Client s3Client;

    @Autowired JdbcTemplate jdbcTemplate;
    @Autowired GeschichteRepository geschichteRepository;
    @Autowired DocumentRepository documentRepository;

    private UUID geschichteId;
    private UUID documentId;

    @BeforeEach
    void seed() {
        jdbcTemplate.execute("DELETE FROM journey_items");
        Document doc = documentRepository.save(Document.builder()
                .title("Constraints-Test-Doc")
                .originalFilename("ct.pdf")
                .status(DocumentStatus.UPLOADED)
                .build());
        documentId = doc.getId();
        Geschichte g = geschichteRepository.save(Geschichte.builder()
                .title("Constraints-Test-Journey")
                .status(GeschichteStatus.DRAFT)
                .type(GeschichteType.JOURNEY)
                .build());
        geschichteId = g.getId();
    }

    @Test
    void unique_constraint_is_deferrable_initially_deferred() {
        Boolean condeferrable = jdbcTemplate.queryForObject(
                "SELECT condeferrable FROM pg_constraint WHERE conname = 'uq_journey_items_geschichte_position'",
                Boolean.class);
        Boolean condeferred = jdbcTemplate.queryForObject(
                "SELECT condeferred FROM pg_constraint WHERE conname = 'uq_journey_items_geschichte_position'",
                Boolean.class);
        assertThat(condeferrable).as("constraint must be deferrable").isTrue();
        assertThat(condeferred).as("constraint must be initially deferred").isTrue();
    }

    @Test
    void unique_index_rejects_duplicate_document_per_geschichte() {
        // Atomic backstop for the service-level dedup pre-check (check-then-insert race).
        jdbcTemplate.update(
                "INSERT INTO journey_items (id, geschichte_id, position, document_id) VALUES (?, ?, ?, ?)",
                UUID.randomUUID(), geschichteId, 10, documentId);
        assertThatThrownBy(() ->
                jdbcTemplate.update(
                        "INSERT INTO journey_items (id, geschichte_id, position, document_id) VALUES (?, ?, ?, ?)",
                        UUID.randomUUID(), geschichteId, 20, documentId))
                .isInstanceOf(DataIntegrityViolationException.class);
    }

    @Test
    void unique_index_allows_same_document_in_different_journeys() {
        Geschichte other = geschichteRepository.save(Geschichte.builder()
                .title("Andere Lesereise")
                .status(GeschichteStatus.DRAFT)
                .type(GeschichteType.JOURNEY)
                .build());
        jdbcTemplate.update(
                "INSERT INTO journey_items (id, geschichte_id, position, document_id) VALUES (?, ?, ?, ?)",
                UUID.randomUUID(), geschichteId, 10, documentId);
        jdbcTemplate.update(
                "INSERT INTO journey_items (id, geschichte_id, position, document_id) VALUES (?, ?, ?, ?)",
                UUID.randomUUID(), other.getId(), 10, documentId);
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM journey_items WHERE document_id = ?", Integer.class, documentId);
        assertThat(count).isEqualTo(2);
    }

    @Test
    void unique_index_allows_multiple_note_only_items() {
        // document_id IS NULL rows must not collide — the index is partial.
        jdbcTemplate.update(
                "INSERT INTO journey_items (id, geschichte_id, position, note) VALUES (?, ?, ?, ?)",
                UUID.randomUUID(), geschichteId, 10, "erste Notiz");
        jdbcTemplate.update(
                "INSERT INTO journey_items (id, geschichte_id, position, note) VALUES (?, ?, ?, ?)",
                UUID.randomUUID(), geschichteId, 20, "zweite Notiz");
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM journey_items WHERE geschichte_id = ?", Integer.class, geschichteId);
        assertThat(count).isEqualTo(2);
    }

    @Test
    void note_length_check_rejects_2001_chars() {
        assertThatThrownBy(() ->
                jdbcTemplate.update(
                        "INSERT INTO journey_items (id, geschichte_id, position, note) VALUES (?, ?, ?, ?)",
                        UUID.randomUUID(), geschichteId, 10, "x".repeat(2001)))
                .isInstanceOf(DataIntegrityViolationException.class);
    }

    @Test
    void note_length_check_accepts_exactly_2000_chars() {
        // Pins the boundary at the DB layer too — a future <= vs < migration edit
        // must fail here, not only in the mock-based service test.
        jdbcTemplate.update(
                "INSERT INTO journey_items (id, geschichte_id, position, note) VALUES (?, ?, ?, ?)",
                UUID.randomUUID(), geschichteId, 10, "x".repeat(2000));
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM journey_items WHERE geschichte_id = ?", Integer.class, geschichteId);
        assertThat(count).isEqualTo(1);
    }

    @Test
    void position_check_rejects_nonpositive() {
        UUID itemId = UUID.randomUUID();
        assertThatThrownBy(() ->
                jdbcTemplate.update(
                        "INSERT INTO journey_items (id, geschichte_id, position, note) VALUES (?, ?, ?, ?)",
                        itemId, geschichteId, 0, "test"))
                .isInstanceOf(DataIntegrityViolationException.class);
    }

    @Test
    void unique_constraint_rejects_duplicate_position_per_geschichte() {
        jdbcTemplate.update(
                "INSERT INTO journey_items (id, geschichte_id, position, document_id) VALUES (?, ?, ?, ?)",
                UUID.randomUUID(), geschichteId, 10, documentId);

        assertThatThrownBy(() ->
                jdbcTemplate.update(
                        "INSERT INTO journey_items (id, geschichte_id, position, document_id) VALUES (?, ?, ?, ?)",
                        UUID.randomUUID(), geschichteId, 10, documentId))
                .isInstanceOf(DataIntegrityViolationException.class);
    }
}
