package space.nstart.pcp.pcp_satellites_service.service

import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import space.nstart.pcp.pcp_satellites_service.configuration.CustomValidationException
import space.nstart.pcp.pcp_satellites_service.dto.ComplexPlanRunCreateDTO
import space.nstart.pcp.pcp_satellites_service.dto.ComplexPlanRunResponseDTO
import space.nstart.pcp.pcp_satellites_service.dto.ComplexPlanRunStatisticsDTO
import space.nstart.pcp.pcp_satellites_service.entity.ComplexPlanRunEntity
import space.nstart.pcp.pcp_types_lib.dto.requests.ComplexPlanProcessRequestDTO
import space.nstart.pcp.pcp_types_lib.dto.requests.CoverageCalculationSource
import tools.jackson.databind.json.JsonMapper
import java.time.Duration
import kotlin.system.measureTimeMillis

@Service
class ComplexPlanRunOrchestrationService(
    private val complexPlanRunPersistenceService: ComplexPlanRunPersistenceService,
    private val complexPlanRunQueryService: ComplexPlanRunQueryService,
    private val complexPlanCalculationService: ComplexPlanCalculationService,
    private val satelliteService: SatelliteService,
    private val complexPlanExecutionLockService: ComplexPlanExecutionLockService
) {

    private val logger = LoggerFactory.getLogger(this::class.java)
    private val objectMapper = JsonMapper.builder().findAndAddModules().build()

    fun process(request: ComplexPlanProcessRequestDTO, requestedBy: String?): ComplexPlanRunResponseDTO {
        validateInterval(request)

        lateinit var selectedSatelliteIds: List<Long>
        val resolveSatelliteIdsMs = measureTimeMillis {
            selectedSatelliteIds = resolveSatelliteIds(request)
        }
        logger.info(
            "Complex plan orchestration stage resolve-satellites completed: satellites={}, durationMs={}",
            selectedSatelliteIds,
            resolveSatelliteIdsMs
        )

        lateinit var createdRun: ComplexPlanRunEntity
         val createRunMs = measureTimeMillis {
            createdRun = complexPlanRunPersistenceService.createRun(
                ComplexPlanRunCreateDTO(
                    intervalStart = request.intervalStart,
                    intervalEnd = request.intervalEnd,
                    satelliteIds = selectedSatelliteIds,
                    requestedBy = requestedBy,
                    requestPayload = objectMapper.writeValueAsString(request)
                )
            )
        }
        val runId = createdRun.id ?: error("Complex plan run id is null after create")
        logger.info(
            "Complex plan orchestration stage create-run completed: runId={}, durationMs={}",
            runId,
            createRunMs
        )

        var startupLock: ComplexPlanExecutionLockService.ExecutionLockHandle? = null
        val acquireLockMs = measureTimeMillis {
            startupLock = complexPlanExecutionLockService.tryAcquire(selectedSatelliteIds)
        }
        logger.info(
            "Complex plan orchestration stage acquire-lock completed: runId={}, acquired={}, durationMs={}",
            runId,
            startupLock != null,
            acquireLockMs
        )
        if (startupLock == null) {
            val errorMessage = "Complex plan recalculation is already running for at least one requested satellite"
            complexPlanRunPersistenceService.markFailed(runId, errorMessage)
            throw CustomValidationException(errorMessage)
        }

        try {
            lateinit var overlappingRunIds: List<Long>
            val overlapCheckMs = measureTimeMillis {
                overlappingRunIds = complexPlanRunPersistenceService.findOverlappingRunningRunIds(
                    satelliteIds = selectedSatelliteIds,
                    intervalStart = request.intervalStart,
                    intervalEnd = request.intervalEnd,
                    excludedRunId = runId
                )
            }
            logger.info(
                "Complex plan orchestration stage overlap-check completed: runId={}, overlaps={}, durationMs={}",
                runId,
                overlappingRunIds,
                overlapCheckMs
            )
            if (overlappingRunIds.isNotEmpty()) {
                val errorMessage =
                    "Complex plan recalculation is already running for overlapping interval, runIds=$overlappingRunIds"
                complexPlanRunPersistenceService.markFailed(runId, errorMessage)
                throw CustomValidationException(errorMessage)
            }

            val markRunningMs = measureTimeMillis {
                complexPlanRunPersistenceService.markRunning(runId)
            }
            logger.info(
                "Complex plan orchestration stage mark-running completed: runId={}, durationMs={}",
                runId,
                markRunningMs
            )
        } finally {
            startupLock?.close()
        }

        val result = try {
            lateinit var calculationResult: ComplexPlanCalculationResult
            val calculationMs = measureTimeMillis {
                calculationResult = complexPlanCalculationService.process(
                    runId = runId,
                    intervalStart = request.intervalStart,
                    intervalEnd = request.intervalEnd,
                    satelliteIds = selectedSatelliteIds,
                    sun = request.sun,
                    coverageSource = request.coverageSource ?: CoverageCalculationSource.SLOTS,
                    countLat = request.countLat,
                    countLong = request.countLong,
                    coverageStrategy = request.coverageStrategy
                )
            }
            logger.info(
                "Complex plan orchestration stage calculation completed: runId={}, calculatedModes={}, durationMs={}",
                runId,
                calculationResult.calculatedModesCount,
                calculationMs
            )
            calculationResult
        } catch (ex: Exception) {
            complexPlanRunPersistenceService.markFailed(runId, ex.message ?: ex.javaClass.simpleName)
            logger.error("Complex plan execution failed: runId={}", runId, ex)
            throw ex
        }
        val markCompletedMs = measureTimeMillis {
            complexPlanRunPersistenceService.markCompleted(
                runId = runId,
                statistics = result.toStatistics()
            )
        }
        logger.info(
            "Complex plan orchestration stage mark-completed completed: runId={}, durationMs={}",
            runId,
            markCompletedMs
        )

        lateinit var response: ComplexPlanRunResponseDTO
        val loadResponseMs = measureTimeMillis {
            response = complexPlanRunQueryService.getRun(runId)
        }
        logger.info(
            "Complex plan orchestration stage load-response completed: runId={}, durationMs={}",
            runId,
            loadResponseMs
        )
        return response
    }

    private fun resolveSatelliteIds(request: ComplexPlanProcessRequestDTO): List<Long> =
        buildList {
            request.satelliteId?.let { add(it) }
            addAll(request.satelliteIds)
        }.distinct().ifEmpty { satelliteService.satellites.map { it.satelliteId }.distinct() }

    /**
     * Интервал расчёта обязан быть корректным и не длиннее [MAX_INTERVAL_DAYS] суток: считать
     * комплексный план по всем КА на больший горизонт слишком тяжело и бессмысленно для оперативного
     * плана. Проверка до создания run, чтобы не плодить заведомо отклонённые записи.
     */
    private fun validateInterval(request: ComplexPlanProcessRequestDTO) {
        if (!request.intervalEnd.isAfter(request.intervalStart)) {
            throw CustomValidationException("Interval end must be after interval start")
        }
        if (Duration.between(request.intervalStart, request.intervalEnd) > Duration.ofDays(MAX_INTERVAL_DAYS)) {
            throw CustomValidationException("Complex plan interval cannot exceed $MAX_INTERVAL_DAYS days")
        }
    }

    companion object {
        /** Максимальная длина интервала расчёта комплексного плана, суток. */
        const val MAX_INTERVAL_DAYS = 10L
    }
}

data class ComplexPlanCalculationResult(
    val calculatedModesCount: Int = 0,
    val bookedModesCount: Int = 0,
    val complanModesCount: Int = 0,
    val mixedModesCount: Int = 0,
    val affectedSatellitesCount: Int = 0,
    val processedCellsCount: Int = 0,
    val bookedSlotLinksCount: Int = 0,
    val totalRequestsArea: Double = 0.0,
    val coveredArea: Double = 0.0,
    val coveredAreaPercent: Double = 0.0
) {
    fun toStatistics(): ComplexPlanRunStatisticsDTO =
        ComplexPlanRunStatisticsDTO(
            calculatedModesCount = calculatedModesCount,
            bookedModesCount = bookedModesCount,
            complanModesCount = complanModesCount,
            mixedModesCount = mixedModesCount,
            affectedSatellitesCount = affectedSatellitesCount,
            processedCellsCount = processedCellsCount,
            bookedSlotLinksCount = bookedSlotLinksCount,
            totalRequestsArea = totalRequestsArea,
            coveredArea = coveredArea,
            coveredAreaPercent = coveredAreaPercent
        )
}
