package org.nstart.dep265.requestservice.service

import com.fasterxml.jackson.databind.ObjectMapper
import org.nstart.dep265.requestservice.entity.OutboxEventStatus
import org.nstart.dep265.requestservice.entity.RequestEntity
import org.nstart.dep265.requestservice.entity.RequestStatus
import org.nstart.dep265.requestservice.repository.OutboxEventRepository
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
import java.util.UUID

/**
 * Складывает в transactional outbox единое событие о **терминальном** статусе заявки
 * (`COMPLETED` или `EXPIRED`) для geoportal. Идемпотентно по `(event_type, aggregate_id)`:
 * на заявку приходится не более одного терминального события.
 *
 * Публикуется [OutboxPublisherService] в топик `pcp.request.status.v1` со статусом в payload.
 */
@Service
class RequestStatusOutboxService(
    private val outboxEventRepository: OutboxEventRepository,

    private val objectMapper: ObjectMapper,

    private val geometryService: GeometryService,
) {
    private val log = LoggerFactory.getLogger(this::class.java)

    @Transactional(propagation = Propagation.MANDATORY)
    fun createTerminalIfAbsent(request: RequestEntity, eventCreatedAt: LocalDateTime) {
        val status = request.status
        require(status == RequestStatus.COMPLETED || status == RequestStatus.EXPIRED) {
            "Терминальное outbox-событие требует COMPLETED/EXPIRED, получено: $status"
        }

        val payload = RequestStatusPayload(
            requestId = request.id,
            status = status.name,
            // Для COMPLETED — момент достижения покрытия, для EXPIRED — момент фиксации истечения.
            occurredAt = request.completedAt ?: eventCreatedAt,
            coveragePercent = geometryService.coveredPercent(request.geometryArea, request.remainingArea),
            matchedAt = request.lastMatchedAt,
            eventCreatedAt = eventCreatedAt,
        )

        val inserted = outboxEventRepository.insertIfAbsent(
            id = UUID.randomUUID(),
            eventType = REQUEST_STATUS_EVENT_TYPE,
            aggregateType = REQUEST_AGGREGATE_TYPE,
            aggregateId = request.id,
            payload = objectMapper.writeValueAsString(payload),
            status = OutboxEventStatus.NEW.name,
            createdAt = eventCreatedAt,
        )

        if (inserted == 0) {
            log.debug(
                "Терминальное outbox-событие уже существует для requestId={}",
                request.id,
            )
        }
    }

    private data class RequestStatusPayload(
        val requestId: UUID,
        val status: String,
        val occurredAt: LocalDateTime,
        val coveragePercent: Double,
        val matchedAt: LocalDateTime?,
        val eventCreatedAt: LocalDateTime,
    )

    companion object {
        const val REQUEST_STATUS_EVENT_TYPE = "REQUEST_TERMINAL_STATUS"
        const val REQUEST_AGGREGATE_TYPE = "REQUEST"
    }
}
