package space.nstart.pcp_tgu_service.service

import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
import org.springframework.transaction.support.TransactionTemplate
import space.nstart.pcp_tgu_service.config.PlanningProperties
import space.nstart.pcp_tgu_service.entity.JobStatus
import space.nstart.pcp_tgu_service.entity.PlanRecalculationJobEntity
import space.nstart.pcp_tgu_service.repository.PlanRecalculationJobRepository
import java.net.InetAddress
import java.time.LocalDateTime

/**
 * Claim'ит jobs перестроения расписания и вызывает RebuildPlanScheduleUseCase.
 */
@Service
class PlanRecalculationWorker(
    private val planningProperties: PlanningProperties,
    private val planRecalculationJobRepository: PlanRecalculationJobRepository,
    private val rebuildPlanScheduleUseCase: RebuildPlanScheduleUseCase,
    private val transactionTemplate: TransactionTemplate
) {
    private val log = LoggerFactory.getLogger(javaClass)
    private val workerId = "${InetAddress.getLocalHost().hostName}-${java.util.UUID.randomUUID()}"

    /** Периодически обрабатывает pending recalculation jobs. */
    @Scheduled(fixedDelayString = "\${planning.notification-check-fixed-delay-ms}")
    fun processPendingJobs() {
        val jobs = claimJobs(limit = 10)
        jobs.forEach { job -> processJob(job.spacecraftId) }
    }

    private fun claimJobs(limit: Int): List<PlanRecalculationJobEntity> =
        transactionTemplate.execute {
            val now = LocalDateTime.now()
            val jobs = planRecalculationJobRepository.findPendingForUpdate(now, limit)
            jobs.forEach { job ->
                job.status = JobStatus.RUNNING
                job.lockedBy = workerId
                job.lockedUntil = now.plusSeconds(planningProperties.workerLockTtlSeconds)
                job.attempts += 1
                job.updatedAt = now
            }
            planRecalculationJobRepository.saveAll(jobs)
            jobs
        }.orEmpty()

    private fun processJob(spacecraftId: String) {
        try {
            rebuildPlanScheduleUseCase.rebuild(spacecraftId)
            markDone(spacecraftId)
        } catch (ex: Exception) {
            log.warn("Plan recalculation job failed. spacecraftId={}", spacecraftId, ex)
            releaseOrFail(spacecraftId)
        }
    }

    private fun markDone(spacecraftId: String) {
        transactionTemplate.executeWithoutResult {
            val job = planRecalculationJobRepository.findById(spacecraftId).orElse(null) ?: return@executeWithoutResult
            job.status = if (job.rerunRequested) JobStatus.PENDING else JobStatus.DONE
            job.rerunRequested = false
            job.lockedBy = null
            job.lockedUntil = null
            job.updatedAt = LocalDateTime.now()
            planRecalculationJobRepository.save(job)
        }
    }

    private fun releaseOrFail(spacecraftId: String) {
        transactionTemplate.executeWithoutResult {
            val job = planRecalculationJobRepository.findById(spacecraftId).orElse(null) ?: return@executeWithoutResult
            job.status = if (job.attempts >= MAX_ATTEMPTS) JobStatus.FAILED else JobStatus.PENDING
            job.lockedBy = null
            job.lockedUntil = null
            job.updatedAt = LocalDateTime.now()
            planRecalculationJobRepository.save(job)
        }
    }

    private companion object {
        const val MAX_ATTEMPTS = 3
    }
}
