package space.nstart.pcp_tgu_service.controller

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.format.annotation.DateTimeFormat
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 reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers
import space.nstart.pcp_tgu_service.domain.PlanDecision
import space.nstart.pcp_tgu_service.dto.PlanDecisionMessage
import space.nstart.pcp_tgu_service.dto.PlanResponse
import space.nstart.pcp_tgu_service.dto.ProcessStartResponse
import space.nstart.pcp_tgu_service.integration.api.ExternalPointsClient
import space.nstart.pcp_tgu_service.service.HandlePlanDecisionUseCase
import space.nstart.pcp_tgu_service.service.IssueDuePlanWorker
import space.nstart.pcp_tgu_service.service.PlanQueryService
import space.nstart.pcp_tgu_service.service.PlanRecalculationJobService
import space.nstart.pcp_tgu_service.service.PlatformService
import space.nstart.pcp_tgu_service.service.RebuildPlanScheduleUseCase
import space.nstart.pcp_tgu_service.service.TestPlanDecisionService
import space.nstart.pcp_tgu_service.service.VisibilityRefreshJobService
import java.nio.file.Files
import java.nio.file.Path
import java.time.LocalDateTime
import java.util.UUID

/**
 * Test-only controller for manual TGU operations. Disabled by default.
 */
@ConditionalOnProperty(prefix = "tgu.test-controller", name = ["enabled"], havingValue = "true")
@RestController
@RequestMapping("/test/tgu")
class TestTguController(
    private val visibilityRefreshJobService: VisibilityRefreshJobService,
    private val planRecalculationJobService: PlanRecalculationJobService,
    private val rebuildPlanScheduleUseCase: RebuildPlanScheduleUseCase,
    private val issueDuePlanWorker: IssueDuePlanWorker,
    private val handlePlanDecisionUseCase: HandlePlanDecisionUseCase,
    private val testPlanDecisionService: TestPlanDecisionService,
    private val externalPointsClient: ExternalPointsClient,
    private val platformService: PlatformService,
    private val planQueryService: PlanQueryService
) {
    /** Ставит refresh job для КА. */
    @PostMapping("/spacecraft/{spacecraftId}/refresh-visibility")
    fun refreshVisibility(
        @PathVariable spacecraftId: String,
        @RequestParam(required = false) noradId: Long?
    ): Mono<Void> =
        Mono.fromRunnable<Void> {
            visibilityRefreshJobService.enqueue(spacecraftId, noradId, "TEST_CONTROLLER")
        }.subscribeOn(Schedulers.boundedElastic())

    /** Ставит и сразу выполняет rebuild persisted schedule для КА. */
    @PostMapping("/spacecraft/{spacecraftId}/rebuild-plans")
    fun rebuildPlans(@PathVariable spacecraftId: String): Mono<Void> =
        Mono.fromRunnable<Void> {
            planRecalculationJobService.enqueue(spacecraftId, "TEST_CONTROLLER")
            rebuildPlanScheduleUseCase.rebuild(spacecraftId)
        }.subscribeOn(Schedulers.boundedElastic())

    /** Принудительно выдаёт конкретный persisted plan через новую attempt. */
    @PostMapping("/plans/{planId}/issue")
    fun issuePlan(@PathVariable planId: UUID): Mono<ProcessStartResponse> =
        Mono.fromCallable {
            issueDuePlanWorker.issuePlanNow(planId)
        }.subscribeOn(Schedulers.boundedElastic())

    /** Инжектит decision event через тот же use case, что Kafka consumer. */
    @PostMapping("/plan-decisions")
    fun handleDecision(@RequestBody message: PlanDecisionMessage): Mono<Void> =
        Mono.fromRunnable<Void> {
            handlePlanDecisionUseCase.handle(message)
        }.subscribeOn(Schedulers.boundedElastic())

    /** Генерирует ACCEPTED/REJECTED decision event для active attempt указанного persisted plan. */
    @PostMapping("/plans/{planId}/decision")
    fun generatePlanDecision(
        @PathVariable planId: UUID,
        @RequestParam decision: PlanDecision,
        @RequestParam(required = false) reason: String?
    ): Mono<PlanDecisionMessage> =
        Mono.fromCallable {
            testPlanDecisionService.generateDecision(planId, decision, reason)
        }.subscribeOn(Schedulers.boundedElastic())

    /** Загружает cache аппаратов из файла с root-array payload НСИ вместо запроса в НСИ. */
    @PostMapping("/platforms/load-from-file")
    fun loadPlatformsFromFile(@RequestParam("path") filePath: String): Mono<TestPlatformsLoadResponse> =
        Mono.fromCallable {
            val path = Path.of(filePath).toAbsolutePath().normalize()
            val platforms = platformService.loadPlatformsFromNsiPayload(Files.readString(path))
            TestPlatformsLoadResponse(
                filePath = path.toString(),
                loadedCount = platforms.size,
                allowedCount = platformService.loadAllowedPlatforms().size
            )
        }.subscribeOn(Schedulers.boundedElastic())

    /** Загружает raw payload окон видимости из баллистики для диагностики. */
    @GetMapping("/external-points/{noradId}")
    fun getExternalPoints(
        @PathVariable noradId: Long,
        @RequestParam("time_start")
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
        timeStart: LocalDateTime,
        @RequestParam("time_stop")
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
        timeStop: LocalDateTime
    ): Mono<String> =
        Mono.fromCallable {
            externalPointsClient.fetchRawVisibilityPayload(noradId, timeStart, timeStop)
        }.subscribeOn(Schedulers.boundedElastic())

    /** Возвращает persisted plan для проверки результата ручных операций. */
    @GetMapping("/plans/{planId}")
    fun getPlan(@PathVariable planId: UUID): Mono<PlanResponse> =
        Mono.fromCallable {
            PlanResponse.from(planQueryService.getByPlanId(planId))
        }.subscribeOn(Schedulers.boundedElastic())
}

data class TestPlatformsLoadResponse(
    val filePath: String,
    val loadedCount: Int,
    val allowedCount: Int
)
