package org.nstart.dep265.requestservice.repository

import jakarta.persistence.LockModeType
import org.nstart.dep265.requestservice.entity.RequestEntity
import org.nstart.dep265.requestservice.entity.RequestStatus
import org.nstart.dep265.requestservice.entity.RequestSurveyType
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Lock
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import java.time.LocalDateTime
import java.util.Optional
import java.util.UUID

interface RequestRepository : JpaRepository<RequestEntity, UUID> {
    @Query(
        value = """
        select distinct request
        from RequestEntity request
        left join request.kpp requestKpp
        where (:includeDeleted = true or request.status <> :deletedStatus)
          and (:status is null or request.status = :status)
          and (:surveyType is null or request.surveyType = :surveyType)
          and (:kpp is null or requestKpp = :kpp)
          and (:highPriorityTransmit is null or request.highPriorityTransmit = :highPriorityTransmit)
          and (cast(:beginFrom as LocalDateTime) is null or request.beginDateTime >= :beginFrom)
          and (cast(:beginTo as LocalDateTime) is null or request.beginDateTime <= :beginTo)
          and (cast(:endFrom as LocalDateTime) is null or request.endDateTime >= :endFrom)
          and (cast(:endTo as LocalDateTime) is null or request.endDateTime <= :endTo)
        """,
        countQuery = """
        select count(distinct request)
        from RequestEntity request
        left join request.kpp requestKpp
        where (:includeDeleted = true or request.status <> :deletedStatus)
          and (:status is null or request.status = :status)
          and (:surveyType is null or request.surveyType = :surveyType)
          and (:kpp is null or requestKpp = :kpp)
          and (:highPriorityTransmit is null or request.highPriorityTransmit = :highPriorityTransmit)
          and (cast(:beginFrom as LocalDateTime) is null or request.beginDateTime >= :beginFrom)
          and (cast(:beginTo as LocalDateTime) is null or request.beginDateTime <= :beginTo)
          and (cast(:endFrom as LocalDateTime) is null or request.endDateTime >= :endFrom)
          and (cast(:endTo as LocalDateTime) is null or request.endDateTime <= :endTo)
        """
    )
    fun findRequests(
        @Param("includeDeleted") includeDeleted: Boolean,
        @Param("deletedStatus") deletedStatus: RequestStatus,
        @Param("status") status: RequestStatus?,
        @Param("surveyType") surveyType: RequestSurveyType?,
        @Param("kpp") kpp: Int?,
        @Param("highPriorityTransmit") highPriorityTransmit: Boolean?,
        @Param("beginFrom") beginFrom: LocalDateTime?,
        @Param("beginTo") beginTo: LocalDateTime?,
        @Param("endFrom") endFrom: LocalDateTime?,
        @Param("endTo") endTo: LocalDateTime?,
        pageable: Pageable,
    ): Page<RequestEntity>

    /**
     * Возвращает идентификаторы активных заявок, окно которых пересекается с окном маршрута.
     */
    @Query(
        """
        select request.id
        from RequestEntity request
        where request.status in :statuses
          and request.beginDateTime <= :routeEnd
          and request.endDateTime >= :routeBegin
          and request.deletedAt is null
        """
    )
    fun findRequestIdsForRoute(
        @Param("statuses") statuses: Collection<RequestStatus>,
        @Param("routeBegin") routeBegin: LocalDateTime,
        @Param("routeEnd") routeEnd: LocalDateTime,
    ): List<UUID>

    /**
     * Сериализует применение маршрута к одной заявке через row-level lock.
     */
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query(
        """
        select request
        from RequestEntity request
        where request.id = :id
        """
    )
    fun findByIdForUpdate(@Param("id") id: UUID): Optional<RequestEntity>

    @Query(
        """
        select request
        from RequestEntity request
        where request.beginDateTime < :selectionEnd
          and :selectionStart < request.endDateTime
          and request.deletedAt is null
        """
    )
    fun findRequestsIntersectingSelectionInterval(
        @Param("selectionStart") selectionStart: LocalDateTime,
        @Param("selectionEnd") selectionEnd: LocalDateTime,
    ): List<RequestEntity>

    fun findByStatusIn(statuses: Collection<RequestStatus>): List<RequestEntity>

    /**
     * Захватывает порцию просроченных заявок (окно уже закончилось, статус ещё незавершённый)
     * под row-level lock. FOR UPDATE SKIP LOCKED позволяет нескольким инстансам разбирать
     * непересекающиеся строки, не конкурируя за одни и те же заявки.
     */
    @Query(
        value = """
            SELECT *
            FROM requests
            WHERE status IN ('ACCEPTED', 'ACTIVE')
              AND end_date_time < :now
              AND deleted_at IS NULL
            ORDER BY end_date_time ASC
            LIMIT :batchSize
            FOR UPDATE SKIP LOCKED
        """,
        nativeQuery = true,
    )
    fun findExpirableForUpdateSkipLocked(
        @Param("now") now: LocalDateTime,
        @Param("batchSize") batchSize: Int,
    ): List<RequestEntity>
}
