package org.nstart.dep265.requestservice.repository

import org.nstart.dep265.requestservice.entity.OutboxEventEntity
import org.nstart.dep265.requestservice.entity.OutboxEventStatus
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import java.time.LocalDateTime
import java.util.UUID

interface OutboxEventRepository : JpaRepository<OutboxEventEntity, UUID> {
    fun existsByEventTypeAndAggregateId(eventType: String, aggregateId: UUID): Boolean

    fun findByStatusOrderByCreatedAtAsc(status: OutboxEventStatus, pageable: Pageable): List<OutboxEventEntity>

    /**
     * Locks publishable events so parallel service instances skip rows already being published by
     * another transaction. Учитывает backoff (`next_attempt_at`) и предел попыток (`attempts`):
     * терминальные FAILED (`attempts >= maxAttempts`) и ещё не «дозревшие» строки исключаются.
     */
    @Query(
        value = """
            SELECT *
            FROM outbox_events
            WHERE event_type = :eventType
              AND status IN ('NEW', 'FAILED')
              AND attempts < :maxAttempts
              AND next_attempt_at <= :now
            ORDER BY
                CASE WHEN status = 'NEW' THEN 0 ELSE 1 END,
                created_at ASC
            LIMIT :batchSize
            FOR UPDATE SKIP LOCKED
        """,
        nativeQuery = true,
    )
    fun findPublishableForUpdateSkipLocked(
        @Param("eventType") eventType: String,
        @Param("now") now: LocalDateTime,
        @Param("maxAttempts") maxAttempts: Int,
        @Param("batchSize") batchSize: Int,
    ): List<OutboxEventEntity>

    /**
     * Inserts a new outbox event atomically and treats the event_type + aggregate_id conflict as
     * an idempotent duplicate. Production PostgreSQL stores payload as JSONB.
     */
    @Modifying
    @Query(
        value = """
            INSERT INTO outbox_events (
                id,
                event_type,
                aggregate_type,
                aggregate_id,
                payload,
                status,
                attempts,
                next_attempt_at,
                created_at,
                published_at,
                error_message
            )
            VALUES (
                :id,
                :eventType,
                :aggregateType,
                :aggregateId,
                CAST(:payload AS jsonb),
                :status,
                0,
                :createdAt,
                :createdAt,
                NULL,
                NULL
            )
            ON CONFLICT (event_type, aggregate_id) DO NOTHING
        """,
        nativeQuery = true,
    )
    fun insertIfAbsent(
        @Param("id") id: UUID,
        @Param("eventType") eventType: String,
        @Param("aggregateType") aggregateType: String,
        @Param("aggregateId") aggregateId: UUID,
        @Param("payload") payload: String,
        @Param("status") status: String,
        @Param("createdAt") createdAt: LocalDateTime,
    ): Int
}
