package org.nstart.dep265.requestservice.repository

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.nstart.dep265.requestservice.PcpRequestServiceApplication
import org.nstart.dep265.requestservice.entity.OutboxEventEntity
import org.nstart.dep265.requestservice.entity.OutboxEventStatus
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.dao.DataIntegrityViolationException
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
import java.util.UUID
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

@SpringBootTest(
    classes = [PcpRequestServiceApplication::class],
    webEnvironment = SpringBootTest.WebEnvironment.NONE,
    properties = [
        "spring.config.import=",
        "spring.cloud.config.enabled=false",
        "spring.flyway.enabled=false",
        "spring.jpa.hibernate.ddl-auto=create-drop",
        "spring.datasource.url=jdbc:h2:mem:outbox-events;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false",
        "spring.datasource.driver-class-name=org.h2.Driver",
        "spring.datasource.username=sa",
        "spring.datasource.password=",
        "spring.jpa.database-platform=org.hibernate.dialect.H2Dialect",
        "spring.kafka.listener.auto-startup=false",
        "spring.kafka.consumer.group-id=request-service-test",
        "app.kafka.topics.route=request-service-test-route",
    ],
)
class OutboxEventRepositoryJpaTest @Autowired constructor(
    private val outboxEventRepository: OutboxEventRepository,
) {
    private val objectMapper = jacksonObjectMapper()

    @BeforeEach
    fun setUp() {
        outboxEventRepository.deleteAll()
    }

    @Test
    fun `unique constraint prevents duplicate request completed event for same request`() {
        val requestId = UUID.fromString("73da563f-448b-4303-a1df-41d618d3cb6d")
        outboxEventRepository.saveAndFlush(outboxEvent(requestId = requestId))

        assertFailsWith<DataIntegrityViolationException> {
            outboxEventRepository.saveAndFlush(outboxEvent(requestId = requestId))
        }
    }

    @Test
    @Transactional
    fun `publishable query returns new events before failed and excludes published events`() {
        val oldFailedEvent = outboxEvent(
            requestId = UUID.fromString("58c9fb48-3930-4c5d-8486-4385130c4706"),
            createdAt = LocalDateTime.parse("2026-01-01T09:00:00"),
            status = OutboxEventStatus.FAILED,
        )
        val newEvent = outboxEvent(
            requestId = UUID.fromString("85cb5fb8-9666-4705-bf02-1376a5d74975"),
            createdAt = LocalDateTime.parse("2026-01-01T10:00:00"),
            status = OutboxEventStatus.NEW,
        )
        val newerFailedEvent = outboxEvent(
            requestId = UUID.fromString("62faf96d-8bd5-46ba-a541-c10e78851f86"),
            createdAt = LocalDateTime.parse("2026-01-01T10:01:00"),
            status = OutboxEventStatus.FAILED,
        )
        val publishedEvent = outboxEvent(
            requestId = UUID.fromString("75051f40-9e67-4f72-9279-b4c64da409d9"),
            status = OutboxEventStatus.PUBLISHED,
        )
        val otherEventType = outboxEvent(
            requestId = UUID.fromString("d911b183-2ba5-493f-81fa-944366db63b5"),
            eventType = "REQUEST_CREATED",
            status = OutboxEventStatus.NEW,
        )
        outboxEventRepository.saveAllAndFlush(listOf(oldFailedEvent, newEvent, newerFailedEvent, publishedEvent, otherEventType))

        val events = outboxEventRepository.findRequestCompletedPublishableForUpdateSkipLocked(
            eventType = "REQUEST_COMPLETED",
            batchSize = 10,
        )

        assertEquals(listOf(newEvent.id, oldFailedEvent.id, newerFailedEvent.id), events.map { it.id })
    }

    private fun outboxEvent(
        requestId: UUID,
        eventType: String = "REQUEST_COMPLETED",
        status: OutboxEventStatus = OutboxEventStatus.NEW,
        createdAt: LocalDateTime = LocalDateTime.parse("2026-01-01T10:00:00"),
    ): OutboxEventEntity {
        return OutboxEventEntity(
            id = UUID.randomUUID(),
            eventType = eventType,
            aggregateType = "REQUEST",
            aggregateId = requestId,
            payload = objectMapper.readTree("""{"requestId":"$requestId","status":"COMPLETED"}"""),
            status = status,
            createdAt = createdAt,
        )
    }
}
