package space.nstart.pcp.slots_service.service

import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.BookedSlotStatus
import space.nstart.pcp.slots_service.entity.BookedSlotEntity
import space.nstart.pcp.slots_service.repository.BookedSlotsRepository

@Service
class BookedSlotStatusService(
    private val bookedSlotsRepository: BookedSlotsRepository
) : BookedSlotStatusUpdater {

    private val logger = LoggerFactory.getLogger(this::class.java)

    @Transactional
    override fun updateBookedSlotsStatus(
        bookedSlotIds: List<Long>,
        targetStatus: BookedSlotStatus,
        context: String
    ): Int {
        val ids = bookedSlotIds.distinct().filter { it > 0 }
        if (ids.isEmpty()) {
            return 0
        }

        val bookedSlots = bookedSlotsRepository.findAllByBookedSlotIdIn(ids)
        val foundIds = bookedSlots.mapNotNull { it.bookedSlotId }.toSet()
        val missingIds = ids.filterNot(foundIds::contains)
        if (missingIds.isNotEmpty()) {
            logger.warn(
                "Booked slots status update skipped missing ids: context={}, status={}, missingIds={}",
                context,
                targetStatus,
                missingIds
            )
        }

        val changed = bookedSlots.filter { bookedSlot ->
            val currentStatus = bookedSlot.currentBookedSlotStatus() ?: return@filter false
            when {
                currentStatus == targetStatus -> false
                isAllowedTransition(currentStatus, targetStatus) -> {
                    bookedSlot.status = targetStatus.name
                    true
                }
                else -> {
                    logger.warn(
                        "Ignoring invalid booked slot transition: id={}, current={}, target={}, context={}",
                        bookedSlot.bookedSlotId,
                        currentStatus,
                        targetStatus,
                        context
                    )
                    false
                }
            }
        }

        if (changed.isNotEmpty()) {
            bookedSlotsRepository.saveAll(changed)
        }

        logger.info(
            "Booked slots status update applied: context={}, targetStatus={}, requested={}, updated={}",
            context,
            targetStatus,
            ids.size,
            changed.size
        )
        return changed.size
    }

    private fun BookedSlotEntity.currentBookedSlotStatus(): BookedSlotStatus? =
        when (status) {
            "CANCELLED" -> BookedSlotStatus.CANCELED
            "CRASHED" -> BookedSlotStatus.FAILED
            "COMPLETED" -> BookedSlotStatus.FINISHED
            else -> runCatching { BookedSlotStatus.valueOf(status) }
                .onFailure {
                    logger.warn("Ignoring booked slot {} with unsupported current status {}", bookedSlotId, status)
                }
                .getOrNull()
        }

    private fun isAllowedTransition(current: BookedSlotStatus, target: BookedSlotStatus): Boolean = when (current) {
        BookedSlotStatus.BOOKED -> target == BookedSlotStatus.PROCESSED || target == BookedSlotStatus.FAILED_TIMEOUT
        BookedSlotStatus.PROCESSED -> target == BookedSlotStatus.FINISHED
            || target == BookedSlotStatus.CANCELED
            || target == BookedSlotStatus.FAILED
            || target == BookedSlotStatus.FAILED_TIMEOUT
        BookedSlotStatus.FINISHED,
        BookedSlotStatus.CANCELED,
        BookedSlotStatus.FAILED,
        BookedSlotStatus.FAILED_TIMEOUT -> false
    }
}

interface BookedSlotStatusUpdater {
    fun updateBookedSlotsStatus(
        bookedSlotIds: List<Long>,
        targetStatus: BookedSlotStatus,
        context: String
    ): Int
}
