package space.nstart.pcp_tgu_service.controller

/** Содержит методы API для чтения текущих планов, создания черновика и ручной выдачи плана. */
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import space.nstart.pcp_tgu_service.dto.CreateChainPlanRequest
import space.nstart.pcp_tgu_service.dto.CreateChainPlanResponse
import space.nstart.pcp_tgu_service.dto.CreateDraftRequest
import space.nstart.pcp_tgu_service.dto.CreateDraftResponse
import space.nstart.pcp_tgu_service.dto.PlanResponse
import space.nstart.pcp_tgu_service.dto.ProcessStartResponse
import space.nstart.pcp_tgu_service.dto.ZrvWindowResponse
import space.nstart.pcp_tgu_service.service.CreateChainPlanUseCase
import space.nstart.pcp_tgu_service.service.CreateDraftUseCase
import space.nstart.pcp_tgu_service.service.DeleteDraftUseCase
import space.nstart.pcp_tgu_service.service.DismissPlanUseCase
import space.nstart.pcp_tgu_service.service.IssueDuePlanWorker
import space.nstart.pcp_tgu_service.service.PlanProjectionService
import space.nstart.pcp_tgu_service.service.PlanQueryService
import java.util.UUID

@RestController
@RequestMapping("/api/plans")
class PlanController(
    private val planQueryService: PlanQueryService,
    private val issueDuePlanWorker: IssueDuePlanWorker,
    private val createDraftUseCase: CreateDraftUseCase,
    private val deleteDraftUseCase: DeleteDraftUseCase,
    private val dismissPlanUseCase: DismissPlanUseCase,
    private val createChainPlanUseCase: CreateChainPlanUseCase,
    private val planProjectionService: PlanProjectionService
) {

    /** Возвращает планы по всем КА (persisted ∪ проекция будущего) с окном истории. */
    @GetMapping
    fun getAllPlans(@RequestParam(defaultValue = "7") historyDays: Long): List<PlanResponse> =
        planQueryService.getAllPlans(historyDays)

    /**
     * Черновики (DRAFT) для режима «Черновики» во вкладке «Планы»: созданные, но ещё не
     * отправленные планы-кандидаты. Отдельный эндпоинт, т.к. остальные чтения DRAFT отсекают.
     * Литеральный путь `/drafts` приоритетнее шаблона `/{spacecraftId}`.
     */
    @GetMapping("/drafts")
    fun getDraftPlans(@RequestParam(required = false) spacecraftId: String?): List<PlanResponse> =
        planQueryService.getDraftPlans(spacecraftId)

    /** Возвращает цепочку планов для одного КА (persisted ∪ проекция будущего). */
    @GetMapping("/{spacecraftId}")
    fun getPlansBySpacecraftId(
        @PathVariable spacecraftId: String,
        @RequestParam(defaultValue = "7") historyDays: Long
    ): List<PlanResponse> =
        planQueryService.getPlansBySpacecraftId(spacecraftId, historyDays)

    /**
     * «Сырые» ЗРВ КА из последнего снапшота (границы реальных зон) — из них оператор набирает окно
     * нового плана во вкладке «Планы». Пусто, если снапшота нет.
     */
    @GetMapping("/{spacecraftId}/zrv")
    fun getZrvWindows(@PathVariable spacecraftId: String): List<ZrvWindowResponse> =
        planProjectionService.rawZrvWindows(spacecraftId).map(ZrvWindowResponse::from)

    /**
     * Создаёт план новой цепочки (действие «Создать план» во вкладке «Планы»): окно из двух ЗРВ,
     * опциональная база-предыстория (или цепочка без истории). Возвращает план-кандидат (DRAFT) и
     * прогноз будущей цепочки от него. План становится рабочим после ручной отправки (promote) и
     * приёмки. 404 — базы нет; 422 — окно не из ЗРВ / база чужого КА / нечисловой spacecraftId.
     */
    @PostMapping("/new-chain")
    fun createChainPlan(@RequestBody body: CreateChainPlanRequest): CreateChainPlanResponse =
        createChainPlanUseCase.createChainPlan(body)

    /**
     * Создаёт черновик плана (DRAFT) на основе basePlanId.
     * Режим replace — черновик в том же слоте (база ещё правима).
     * Режим fork-forward — черновик на следующей ЗРВ (база невозвратна).
     * Возвращает draftPlanId, с которым работают Ф3–Ф5.
     */
    @PostMapping("/{planId}/draft")
    fun createDraft(
        @PathVariable planId: UUID,
        @RequestBody body: CreateDraftRequest
    ): CreateDraftResponse = createDraftUseCase.createDraft(planId, body)

    /**
     * Ручная выдача плана оператором («Применить / Отправить вручную»). Стартует новую attempt
     * и Camunda-процесс под гардом «точки невозврата»: план должен быть в будущем за порогом заморозки,
     * не заложен (≠ LAID_IN) и по КА не должно быть активной выдачи.
     * 404 — плана нет; 409 — по КА уже идёт выдача; 422 — точка невозврата пройдена (нужен fork-forward).
     */
    @PostMapping("/{planId}/issue")
    fun issuePlan(@PathVariable planId: UUID): ProcessStartResponse =
        issueDuePlanWorker.issuePlanNow(planId)

    /**
     * «Применить» черновик (Ф5): промоутит DRAFT-план в живую цепочку и выдаёт его. Под lock'ом
     * состояния КА: оптимистичный лок базы (версия цепочки не уехала), контроль валидности слота
     * и гард точки невозврата.
     * 404 — черновика нет; 422 — план не DRAFT / слот сместился / точка невозврата пройдена;
     * 409 — база изменилась с момента создания черновика либо по КА уже идёт выдача.
     */
    @PostMapping("/{draftPlanId}/promote")
    fun promoteDraft(@PathVariable draftPlanId: UUID): ProcessStartResponse =
        issueDuePlanWorker.promoteDraftNow(draftPlanId)

    /**
     * Отказ от черновика (Ф6): удаляет DRAFT-план и его клон-миссию, снимая блокировку «один
     * открытый черновик на базу». 404 — плана нет; 422 — план не DRAFT (живые планы не трогаем).
     */
    @DeleteMapping("/{draftPlanId}")
    fun deleteDraft(@PathVariable draftPlanId: UUID): ResponseEntity<Void> {
        deleteDraftUseCase.deleteDraft(draftPlanId)
        return ResponseEntity.noContent().build()
    }

    /**
     * «Снять с учёта» терминально-негативный план: проставляет `dismissedAt`, чтобы он перестал
     * считаться проблемой и красить индикатор КА. Статус и цепочку не трогает.
     * 404 — плана нет; 422 — статус не терминально-негативный.
     */
    @PostMapping("/{planId}/dismiss")
    fun dismissPlan(@PathVariable planId: UUID): ResponseEntity<Void> {
        dismissPlanUseCase.dismiss(planId)
        return ResponseEntity.noContent().build()
    }

    /** «Вернуть на учёт»: снимает `dismissedAt`. 404 — плана нет. */
    @PostMapping("/{planId}/restore")
    fun restorePlan(@PathVariable planId: UUID): ResponseEntity<Void> {
        dismissPlanUseCase.restore(planId)
        return ResponseEntity.noContent().build()
    }
}
