package space.nstart.pcp.slots_service.service

import org.springframework.beans.factory.ObjectProvider
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import space.nstart.pcp.pcp_types_lib.dto.requests.ComplexPlanProcessRequestDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteInfoDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSummaryDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SatelliteModeResponseDTO
import space.nstart.pcp.slots_service.configuration.CustomErrorException
import tools.jackson.databind.JsonNode
import tools.jackson.databind.ObjectMapper
import java.time.LocalDateTime


@Service
class ComplexMissionService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>) {
    private val webClientBuilder: WebClient.Builder = webClientBuilderProvider.ifAvailable ?: WebClient.builder()

    @Value("\${settings.complex-mission-service:pcp-complex-mission-service}")
    val complexMissionUrl = ""

    @Value("\${settings.satellite-catalog-service:\${settings.complex-mission-service:pcp-satellite-catalog-service}}")
    val satelliteCatalogUrl = ""

    fun satellites() =
        webClientBuilder.build()
        .get()
        .uri("$satelliteCatalogUrl/api/satellites")
        .retrieve()
        .bodyToFlux(SatelliteSummaryDTO::class.java)
        .map { it.toLegacyInfo() }

    fun satellite(sat : Long) =
        webClientBuilder.build()
            .get()
            .uri("$satelliteCatalogUrl/api/satellites/$sat")
            .retrieve()
            .bodyToMono(SatelliteDTO::class.java)
            .map { it.toLegacyInfo() }

    fun plan(sat : Long, tn : LocalDateTime, tk : LocalDateTime, runId: Long? = null) : Flux<SatelliteModeResponseDTO> {
        val runIdParam = runId?.let { "&runId=$it" }.orEmpty()
        val s = "$complexMissionUrl/api/satellites/$sat/modes?intervalStart=$tn&intervalEnd=$tk$runIdParam"
        return webClientBuilder.build()
            .get()
            .uri(s)
            .retrieve()
            .bodyToFlux(SatelliteModeResponseDTO::class.java)
    }

    fun processComplexPlan(request: ComplexPlanProcessRequestDTO): JsonNode =
        webClientBuilder.build()
            .post()
            .uri("$complexMissionUrl/api/com-plan/process")
            .header("X-Requested-By", "pcp-ui-service")
            .bodyValue(request)
            .retrieve()
            .onStatus({ status -> status.isError }) { response ->
                response.bodyToMono(String::class.java)
                    .defaultIfEmpty("error")
                    .flatMap { body -> Mono.error(CustomErrorException(extractErrorMessage(body))) }
            }
            .bodyToMono(JsonNode::class.java)
            .block() ?: ObjectMapper().createObjectNode()

    fun complexPlanRuns(): JsonNode =
        webClientBuilder.build()
            .get()
            .uri("$complexMissionUrl/api/com-plan/runs")
            .retrieve()
            .onStatus({ status -> status.isError }) { response ->
                response.bodyToMono(String::class.java)
                    .defaultIfEmpty("error")
                    .flatMap { body -> Mono.error(CustomErrorException(extractErrorMessage(body))) }
            }
            .bodyToMono(JsonNode::class.java)
            .block() ?: ObjectMapper().createArrayNode()

    fun complexPlanRun(id: Long): JsonNode =
        webClientBuilder.build()
            .get()
            .uri("$complexMissionUrl/api/com-plan/runs/$id")
            .retrieve()
            .onStatus({ status -> status.isError }) { response ->
                response.bodyToMono(String::class.java)
                    .defaultIfEmpty("error")
                    .flatMap { body -> Mono.error(CustomErrorException(extractErrorMessage(body))) }
            }
            .bodyToMono(JsonNode::class.java)
            .block() ?: ObjectMapper().createObjectNode()

    fun complexPlanRunModes(id: Long): JsonNode =
        webClientBuilder.build()
            .get()
            .uri("$complexMissionUrl/api/com-plan/runs/$id/modes")
            .retrieve()
            .onStatus({ status -> status.isError }) { response ->
                response.bodyToMono(String::class.java)
                    .defaultIfEmpty("error")
                    .flatMap { body -> Mono.error(CustomErrorException(extractErrorMessage(body))) }
            }
            .bodyToMono(JsonNode::class.java)
            .block() ?: ObjectMapper().createArrayNode()

    fun deleteComplexPlanRun(id: Long) {
        webClientBuilder.build()
            .delete()
            .uri("$complexMissionUrl/api/com-plan/runs/$id")
            .retrieve()
            .onStatus({ status -> status.isError }) { response ->
                response.bodyToMono(String::class.java)
                    .defaultIfEmpty("error")
                    .flatMap { body -> Mono.error(CustomErrorException(extractErrorMessage(body))) }
            }
            .toBodilessEntity()
            .block()
    }

    private fun extractErrorMessage(body: String): String {
        val text = body.trim()
        if (text.isEmpty()) return "error"

        return runCatching {
            val node = ObjectMapper().readTree(text)
            when {
                node.hasNonNull("error") -> node["error"].asText()
                node.hasNonNull("message") -> node["message"].asText()
                else -> text
            }
        }.getOrDefault(text)
    }

    private fun SatelliteSummaryDTO.toLegacyInfo() = SatelliteInfoDTO(
        noradId = id,
        name = name,
        red = visualization.red,
        green = visualization.green,
        blue = visualization.blue,
        scanTLE = scanTle,
        code = code,
        typeCode = typeCode,
        active = active
    )

    private fun SatelliteDTO.toLegacyInfo(): SatelliteInfoDTO {
        return SatelliteInfoDTO(
            noradId = id,
            name = name,
            red = visualization.red,
            green = visualization.green,
            blue = visualization.blue,
            captureAngle = observationProfile?.captureAngle ?: 1.5,
            scanTLE = scanTle,
            maxSurveyDurationSeconds = observationProfile?.durationMaxSeconds,
            mmiSeconds = observationProfile?.mmiSeconds,
            code = code,
            typeCode = typeCode,
            active = active
        )
    }

}
