package space.nstart.pcp_tgu_service.service

import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException
import space.nstart.pcp_tgu_service.domain.CalculatedPlan
import space.nstart.pcp_tgu_service.dto.PlanResponse
import space.nstart.pcp_tgu_service.entity.PlannedPlanEntity
import space.nstart.pcp_tgu_service.entity.PlannedPlanStatus
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
import space.nstart.pcp_tgu_service.repository.SpacecraftSnapshotRepository
import java.time.LocalDateTime
import java.util.UUID
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes

/**
 * Read-сервис планов: объединяет persisted (выданные, история — вечно) с вычисленной
 * на лету проекцией будущего ([PlanProjectionService]). Проекция в БД не пишется.
 */
@Service
class PlanQueryService(
    private val plannedPlanRepository: PlannedPlanRepository,
    private val spacecraftSnapshotRepository: SpacecraftSnapshotRepository,
    private val planProjectionService: PlanProjectionService
) {
    /** Все планы (persisted ∪ проекция) с отсечением глубокой истории по `historyDays`. */
    @Transactional(readOnly = true)
    fun getAllPlans(historyDays: Long): List<PlanResponse> {
        val historySince = historySince(historyDays)
        val persisted = plannedPlanRepository.findAllByOrderByStartTime()
            .filter { plan -> plan.endTime >= historySince && plan.status != PlannedPlanStatus.DRAFT }
        val persistedKeys = persisted.mapTo(HashSet()) { plan -> plan.businessKey() }

        val projected = spacecraftSnapshotRepository.findAllByActiveTrue()
            .flatMap { snapshot -> planProjectionService.project(snapshot.spacecraftId) }
            .filter { plan -> plan.businessKey() !in persistedKeys }

        return combine(persisted, projected)
    }

    /** Планы одного КА (persisted ∪ проекция) с отсечением глубокой истории. */
    @Transactional(readOnly = true)
    fun getPlansBySpacecraftId(spacecraftId: String, historyDays: Long): List<PlanResponse> {
        val historySince = historySince(historyDays)
        val persisted = plannedPlanRepository.findAllBySpacecraftIdOrderByStartTime(spacecraftId)
            .filter { plan -> plan.endTime >= historySince && plan.status != PlannedPlanStatus.DRAFT }
        val persistedKeys = persisted.mapTo(HashSet()) { plan -> plan.businessKey() }

        val projected = planProjectionService.project(spacecraftId)
            .filter { plan -> plan.businessKey() !in persistedKeys }

        return combine(persisted, projected)
    }

    /**
     * Черновики (DRAFT) — отдельным чтением, т.к. основной read-path их намеренно отсекает.
     * Для режима «Черновики» во вкладке «Планы»: список созданных, но ещё не отправленных
     * планов-кандидатов (из диалога новой цепочки или из редактора). Опционально по одному КА.
     */
    @Transactional(readOnly = true)
    fun getDraftPlans(spacecraftId: String?): List<PlanResponse> {
        val plans = if (spacecraftId != null) {
            plannedPlanRepository.findAllBySpacecraftIdOrderByStartTime(spacecraftId)
        } else {
            plannedPlanRepository.findAllByOrderByStartTime()
        }
        return plans
            .filter { plan -> plan.status == PlannedPlanStatus.DRAFT }
            .map(PlanResponse::from)
    }

    /** Возвращает persisted plan по ID для test-only/debug API. */
    @Transactional(readOnly = true)
    fun getByPlanId(planId: UUID): PlannedPlanEntity =
        plannedPlanRepository.findById(planId)
            .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Plan with planId=$planId was not found") }

    private fun combine(persisted: List<PlannedPlanEntity>, projected: List<CalculatedPlan>): List<PlanResponse> =
        (persisted.map(PlanResponse::from) + projected.map(PlanResponse::projected))
            .sortedBy { response -> response.startTime }

    private fun historySince(historyDays: Long): LocalDateTime =
        UtcDateTimes.now().minusDays(historyDays.coerceAtLeast(0))

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

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