package space.nstart.pcp_tgu_service.service

import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import space.nstart.pcp_tgu_service.config.PlanningProperties
import space.nstart.pcp_tgu_service.domain.CalculatedPlan
import space.nstart.pcp_tgu_service.entity.PlannedPlanEntity
import space.nstart.pcp_tgu_service.entity.PlannedPlanStatus
import space.nstart.pcp_tgu_service.entity.SpacecraftPlanningStateEntity
import space.nstart.pcp_tgu_service.integration.api.VisibilityPayloadParser
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
import space.nstart.pcp_tgu_service.repository.SpacecraftVisibilitySnapshotRepository
import java.time.LocalDateTime
import java.util.UUID

/**
 * Перестраивает persisted расписание планов из последнего raw visibility snapshot.
 */
@Service
class RebuildPlanScheduleUseCase(
    private val spacecraftVisibilitySnapshotRepository: SpacecraftVisibilitySnapshotRepository,
    private val spacecraftPlanningStateJpaRepository: SpacecraftPlanningStateJpaRepository,
    private val plannedPlanRepository: PlannedPlanRepository,
    private val visibilityPayloadParser: VisibilityPayloadParser,
    private val planCalculationService: PlanCalculationService,
    private val planningProperties: PlanningProperties
) {
    /** Rebuild по одному КА: upsert сохраняет стабильный planId для business key. */
    @Transactional
    fun rebuild(spacecraftId: String) {
        val snapshot = spacecraftVisibilitySnapshotRepository.findById(spacecraftId)
            .orElseThrow { IllegalStateException("Visibility snapshot for spacecraftId=$spacecraftId was not found") }
        val points = visibilityPayloadParser.parse(spacecraftId, snapshot.payloadJson)
        val state = getOrCreateState(spacecraftId)
        val basePlan = state.currentBasePlanId?.let { planId -> plannedPlanRepository.findById(planId).orElse(null) }

        val calculatedPlans = if (basePlan == null) {
            planCalculationService.calculatePlans(points)
        } else {
            planCalculationService.calculatePlansAfter(points, basePlan.toCalculatedPlan())
        }

        val now = LocalDateTime.now()
        val chainVersion = state.chainVersion + 1
        val savedPlans = calculatedPlans.map { calculatedPlan ->
            upsertPlan(calculatedPlan, state.currentBasePlanId, chainVersion, now)
        }

        supersedeObsoleteFuturePlans(
            spacecraftId = spacecraftId,
            calculatedBusinessKeys = savedPlans.map { plan -> plan.businessKey() }.toSet(),
            now = now
        )
        state.chainVersion = chainVersion
        state.updatedAt = now
        spacecraftPlanningStateJpaRepository.save(state)
    }

    private fun getOrCreateState(spacecraftId: String): SpacecraftPlanningStateEntity {
        spacecraftPlanningStateJpaRepository.findById(spacecraftId).orElse(null)
            ?: spacecraftPlanningStateJpaRepository.saveAndFlush(SpacecraftPlanningStateEntity(spacecraftId = spacecraftId))
        return spacecraftPlanningStateJpaRepository.lockBySpacecraftId(spacecraftId)
            ?: throw IllegalStateException("Planning state for spacecraftId=$spacecraftId was not found after create")
    }

    private fun upsertPlan(
        calculatedPlan: CalculatedPlan,
        basePlanId: UUID?,
        chainVersion: Long,
        now: LocalDateTime
    ): PlannedPlanEntity =
        plannedPlanRepository.upsertPlannedByBusinessKey(
            planId = UUID.randomUUID(),
            spacecraftId = calculatedPlan.spacecraftId,
            startTime = calculatedPlan.startTime,
            endTime = calculatedPlan.endTime,
            kppId = calculatedPlan.kppId,
            issueNotBefore = calculatedPlan.startTime.minusMinutes(planningProperties.notificationBeforeStartMinutes),
            basePlanId = basePlanId,
            chainVersion = chainVersion,
            now = now
        )

    private fun supersedeObsoleteFuturePlans(
        spacecraftId: String,
        calculatedBusinessKeys: Set<String>,
        now: LocalDateTime
    ) {
        plannedPlanRepository.findAllBySpacecraftIdAndStatusAndEndTimeAfterOrderByStartTime(
            spacecraftId = spacecraftId,
            status = PlannedPlanStatus.PLANNED,
            endTime = now
        )
            .filter { plan -> plan.businessKey() !in calculatedBusinessKeys }
            .forEach { plan ->
                plan.status = PlannedPlanStatus.SUPERSEDED
                plan.updatedAt = now
                plannedPlanRepository.save(plan)
            }
    }

    private fun PlannedPlanEntity.toCalculatedPlan(): CalculatedPlan =
        CalculatedPlan(
            spacecraftId = spacecraftId,
            startTime = startTime,
            endTime = endTime,
            kppId = kppId
        )

    private fun PlannedPlanEntity.businessKey(): String =
        "$spacecraftId|$startTime|$endTime|$kppId"
}
