package space.nstart.pcp_tgu_service.dto

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 java.nio.charset.StandardCharsets
import java.time.LocalDateTime
import java.util.UUID

/**
 * DTO ответа REST API для плана.
 *
 * Времена ([startTime]/[endTime]) — UTC-naive `LocalDateTime` в домене/БД.
 * На REST-границе сериализуются как ISO-8601 UTC с `Z` (например `"2026-05-29T01:00:00Z"`).
 */
data class PlanResponse(

    /** UUID-идентификатор плана */
    val planId: UUID,

    /** Идентификатор космического аппарата */
    val spacecraftId: String,

    /** Время начала плана (UTC). */
    val startTime: LocalDateTime,

    /** Время окончания плана (UTC). */
    val endTime: LocalDateTime,

    /** Идентификатор пункта закладки */
    val kppId: String,

    /** Статус persisted плана в жизненном цикле выдачи. */
    val status: PlannedPlanStatus,

    /**
     * Непосредственный предшественник плана в цепочке (`base_plan_id`). `null` — у плана нет
     * предыстории: это начало новой цепочки (создан «без истории») либо самый первый план.
     * Фронт по этому полю решает, рисовать ли входящую стрелку: в старт цепочки она не ведёт.
     */
    val basePlanId: UUID? = null,

    /**
     * `true` — это вычисленная на лету проекция будущего (в БД не записана), а не persisted-план.
     * У проекции предшественник не хранится, но цепочку она продолжает — фронт связывает её
     * соседние звенья по времени, поэтому начало цепочки определяется ТОЛЬКО для persisted.
     */
    val projected: Boolean = false,

    /**
     * Время «снятия с учёта» (деактивации) терминально-негативного плана оператором. `null` — план
     * на учёте. Фронт по этому полю гасит план и исключает его из индикатора проблем КА.
     */
    val dismissedAt: LocalDateTime? = null
) {

    companion object {

        /**
         * Преобразует доменную сущность плана в DTO ответа.
         */
        fun from(plan: PlannedPlanEntity): PlanResponse =
            PlanResponse(
                planId = plan.planId,
                spacecraftId = plan.spacecraftId,
                startTime = plan.startTime,
                endTime = plan.endTime,
                kppId = plan.kppId,
                status = plan.status,
                basePlanId = plan.basePlanId,
                projected = false,
                dismissedAt = plan.dismissedAt
            )

        /**
         * Виртуальный (не сохранённый) план будущей проекции со статусом PLANNED.
         *
         * planId детерминированно выводится из бизнес-ключа, чтобы один и тот же расчётный план
         * стабильно опознавался фронтом между запросами.
         */
        fun projected(plan: CalculatedPlan): PlanResponse =
            PlanResponse(
                planId = deterministicPlanId(plan),
                spacecraftId = plan.spacecraftId,
                startTime = plan.startTime,
                endTime = plan.endTime,
                kppId = plan.kppId,
                status = PlannedPlanStatus.PLANNED,
                basePlanId = null,
                projected = true
            )

        private fun deterministicPlanId(plan: CalculatedPlan): UUID {
            val businessKey = "${plan.spacecraftId}|${plan.startTime}|${plan.endTime}|${plan.kppId}"
            return UUID.nameUUIDFromBytes(businessKey.toByteArray(StandardCharsets.UTF_8))
        }
    }
}
