package space.nstart.pcp.pcp_satellites_service.service

import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
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.ComplexPlanRunStatisticsDTO
import space.nstart.pcp.pcp_satellites_service.entity.ComplexPlanRunEntity
import space.nstart.pcp.pcp_satellites_service.entity.ComplexPlanRunSatelliteEntity
import space.nstart.pcp.pcp_satellites_service.entity.ComplexPlanRunStatus
import space.nstart.pcp.pcp_satellites_service.repository.ComplexPlanRunRepository
import java.time.Clock
import java.time.Duration
import java.time.LocalDateTime

@Service
class ComplexPlanRunPersistenceService(
    private val complexPlanRunRepository: ComplexPlanRunRepository,
    private val clock: Clock
) {

    companion object {
        const val APPLICATION_SHUTDOWN_ERROR_MESSAGE = "Run marked as FAILED because the application was shutting down"
        const val STARTUP_RECOVERY_ERROR_MESSAGE = "Marked as FAILED on startup: previous process terminated before completion"
    }

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

    @Transactional
    fun createRun(createDTO: ComplexPlanRunCreateDTO): ComplexPlanRunEntity {
        validateInterval(createDTO.intervalStart, createDTO.intervalEnd)
        val now = LocalDateTime.now(clock)
        val entity = ComplexPlanRunEntity(
            status = ComplexPlanRunStatus.CREATED,
            createdAt = now,
            intervalStart = createDTO.intervalStart,
            intervalEnd = createDTO.intervalEnd,
            requestedBy = createDTO.requestedBy,
            requestPayload = createDTO.requestPayload
        )
        entity.satellites = createDTO.satelliteIds
            .distinct()
            .sorted()
            .map { satelliteId ->
                ComplexPlanRunSatelliteEntity(
                    run = entity,
                    satelliteId = satelliteId
                )
            }
            .toMutableList()

        val saved = complexPlanRunRepository.save(entity)
        logger.info(
            "complexPlanRun created: runId={}, satellites={}, interval=[{} - {}], requestedBy={}",
            saved.id,
            saved.satellites.map { it.satelliteId },
            saved.intervalStart,
            saved.intervalEnd,
            saved.requestedBy
        )
        return saved
    }

    @Transactional
    fun markRunning(runId: Long): ComplexPlanRunEntity {
        val entity = getRunOrThrow(runId)
        entity.status = ComplexPlanRunStatus.RUNNING
        entity.startedAt = LocalDateTime.now(clock)
        entity.finishedAt = null
        entity.durationMs = null
        entity.errorMessage = null
        logger.info("complexPlanRun started: runId={}, startedAt={}", runId, entity.startedAt)
        return entity
    }

    @Transactional
    fun markCompleted(runId: Long, statistics: ComplexPlanRunStatisticsDTO): ComplexPlanRunEntity {
        val entity = getRunOrThrow(runId)
        entity.status = ComplexPlanRunStatus.COMPLETED
        entity.finishedAt = LocalDateTime.now(clock)
        entity.durationMs = calculateDurationMs(entity.startedAt, entity.finishedAt)
        entity.errorMessage = null
        applyStatistics(entity, statistics)
        logger.info(
            "complexPlanRun completed: runId={}, durationMs={}, calculatedModes={}, bookedModes={}, complanModes={}, mixedModes={}, satellites={}, processedCells={}, bookedSlotLinks={}",
            runId,
            entity.durationMs,
            entity.calculatedModesCount,
            entity.bookedModesCount,
            entity.complanModesCount,
            entity.mixedModesCount,
            entity.affectedSatellitesCount,
            entity.processedCellsCount,
            entity.bookedSlotLinksCount
        )
        return entity
    }

    @Transactional
    fun markFailed(runId: Long, errorMessage: String?): ComplexPlanRunEntity {
        val entity = getRunOrThrow(runId)
        entity.status = ComplexPlanRunStatus.FAILED
        entity.finishedAt = LocalDateTime.now(clock)
        entity.durationMs = calculateDurationMs(entity.startedAt, entity.finishedAt)
        entity.errorMessage = errorMessage?.take(4000)
        logger.info(
            "complexPlanRun failed: runId={}, durationMs={}, error={}",
            runId,
            entity.durationMs,
            entity.errorMessage
        )
        return entity
    }

    @Transactional
    fun markActiveRunsFailedOnShutdown(
        errorMessage: String = APPLICATION_SHUTDOWN_ERROR_MESSAGE
    ): Int {
        val interruptedStatuses = listOf(
            ComplexPlanRunStatus.CREATED,
            ComplexPlanRunStatus.RUNNING
        )
        val now = LocalDateTime.now(clock)
        val interruptedRuns = complexPlanRunRepository.findAllByStatusIn(interruptedStatuses)

        interruptedRuns.forEach { entity ->
            entity.status = ComplexPlanRunStatus.FAILED
            entity.finishedAt = now
            entity.durationMs = calculateDurationMs(entity.startedAt, entity.finishedAt)
            entity.errorMessage = errorMessage.take(4000)
        }

        if (interruptedRuns.isNotEmpty()) {
            logger.info(
                "complexPlanRun shutdown-fail: affectedRuns={}, runIds={}",
                interruptedRuns.size,
                interruptedRuns.mapNotNull { it.id }.sorted()
            )
        }

        return interruptedRuns.size
    }

    @Transactional
    fun markRunningRunsFailedOnStartup(
        errorMessage: String = STARTUP_RECOVERY_ERROR_MESSAGE
    ): Int {
        val now = LocalDateTime.now(clock)
        val staleRuns = complexPlanRunRepository.findAllByStatusIn(listOf(ComplexPlanRunStatus.RUNNING))

        staleRuns.forEach { entity ->
            entity.status = ComplexPlanRunStatus.FAILED
            entity.finishedAt = entity.finishedAt ?: now
            entity.durationMs = calculateDurationMs(entity.startedAt, entity.finishedAt)
            entity.errorMessage = errorMessage.take(4000)
        }

        if (staleRuns.isNotEmpty()) {
            logger.info(
                "complexPlanRun startup-recovery: affectedRuns={}, runIds={}",
                staleRuns.size,
                staleRuns.mapNotNull { it.id }.sorted()
            )
        }

        return staleRuns.size
    }

    @Transactional(readOnly = true)
    fun getRun(runId: Long): ComplexPlanRunEntity {
        val entity = complexPlanRunRepository.findDetailedById(runId)
            ?: throw CustomValidationException("Complex plan run $runId not found")
        entity.snapshots.size
        return entity
    }

    @Transactional(readOnly = true)
    fun findOverlappingRunningRunIds(
        satelliteIds: Collection<Long>,
        intervalStart: LocalDateTime,
        intervalEnd: LocalDateTime,
        excludedRunId: Long
    ): List<Long> {
        validateInterval(intervalStart, intervalEnd)
        if (satelliteIds.isEmpty()) {
            return emptyList()
        }

        return complexPlanRunRepository.findOverlappingRunIdsByStatusAndSatelliteIds(
            status = ComplexPlanRunStatus.RUNNING,
            satelliteIds = satelliteIds.distinct(),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            excludedRunId = excludedRunId
        )
    }

    @Transactional
    fun deleteRun(runId: Long) {
        if (!complexPlanRunRepository.existsById(runId)) {
            throw CustomValidationException("Complex plan run $runId not found")
        }

        complexPlanRunRepository.deleteById(runId)
        logger.info("complexPlanRun deleted: runId={}", runId)
    }

    private fun getRunOrThrow(runId: Long): ComplexPlanRunEntity =
        complexPlanRunRepository.findById(runId)
            .orElseThrow { CustomValidationException("Complex plan run $runId not found") }

    private fun applyStatistics(entity: ComplexPlanRunEntity, statistics: ComplexPlanRunStatisticsDTO) {
        entity.calculatedModesCount = statistics.calculatedModesCount
        entity.bookedModesCount = statistics.bookedModesCount
        entity.complanModesCount = statistics.complanModesCount
        entity.mixedModesCount = statistics.mixedModesCount
        entity.affectedSatellitesCount = statistics.affectedSatellitesCount
        entity.processedCellsCount = statistics.processedCellsCount
        entity.bookedSlotLinksCount = statistics.bookedSlotLinksCount
        entity.totalRequestsArea = statistics.totalRequestsArea
        entity.coveredArea = statistics.coveredArea
        entity.coveredAreaPercent = statistics.coveredAreaPercent
    }

    private fun calculateDurationMs(startedAt: LocalDateTime?, finishedAt: LocalDateTime?): Long? {
        if (startedAt == null || finishedAt == null) {
            return null
        }
        return Duration.between(startedAt, finishedAt).toMillis().coerceAtLeast(0)
    }

    private fun validateInterval(intervalStart: LocalDateTime, intervalEnd: LocalDateTime) {
        if (intervalEnd.isBefore(intervalStart)) {
            throw CustomValidationException("Параметр intervalEnd должен быть больше или равен intervalStart")
        }
    }
}
