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.Mono
import space.nstart.pcp.pcp_types_lib.dto.ballistics.AngularMotionCalculationRequestDTO
import space.nstart.pcp.slots_service.configuration.CustomErrorException
import space.nstart.pcp.slots_service.dto.dynamicplan.DynamicPlanCalculationRequestDTO
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import tools.jackson.databind.JsonNode
import tools.jackson.databind.ObjectMapper
import java.net.URI
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.time.LocalDateTime
import java.util.UUID

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

    @Value("\${settings.dynamic-plan-service:pcp-dynamic-plan-service}")
    private lateinit var dynamicPlanUrl: String

    @Value("\${settings.ballistics-service:pcp-ballistics-service}")
    private lateinit var ballisticsServiceUrl: String

    fun calculate(request: DynamicPlanCalculationRequestDTO): JsonNode =
        webClientBuilder.build()
            .post()
            .uri("$dynamicPlanUrl/v1/main/calcPlan")
            .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 getRun(runId: UUID): JsonNode =
        webClientBuilder.build()
            .get()
            .uri("$dynamicPlanUrl/v1/main/calcPlan/$runId")
            .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 getRuns(status: String?, limit: Int, offset: Int): JsonNode {
        val statusParam = status
            ?.takeIf { value -> value.isNotBlank() }
            ?.let { value -> "&status=${URLEncoder.encode(value, StandardCharsets.UTF_8)}" }
            ?: ""

        return webClientBuilder.build()
            .get()
            .uri("$dynamicPlanUrl/v1/main/calcPlan/runs?limit=$limit&offset=$offset$statusParam")
            .retrieve()
            .onStatus({ statusCode -> statusCode.isError }) { response ->
                response.bodyToMono(String::class.java)
                    .defaultIfEmpty("error")
                    .flatMap { body -> Mono.error(CustomErrorException(extractErrorMessage(body))) }
            }
            .bodyToMono(JsonNode::class.java)
            .block() ?: ObjectMapper().createArrayNode()
    }

    fun getResult(runId: UUID): JsonNode =
        webClientBuilder.build()
            .get()
            .uri("$dynamicPlanUrl/v1/main/calcPlan/$runId/result")
            .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 getRoutes(runId: UUID, limit: Int, offset: Int): JsonNode =
        webClientBuilder.build()
            .get()
            .uri("$dynamicPlanUrl/v1/main/calcPlan/$runId/routes?limit=$limit&offset=$offset")
            .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 getIntervals(runId: UUID, limit: Int, offset: Int): JsonNode =
        webClientBuilder.build()
            .get()
            .uri("$dynamicPlanUrl/v1/main/calcPlan/$runId/intervals?limit=$limit&offset=$offset")
            .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 getAngularMotionSatellites(time: LocalDateTime?): JsonNode {
        val timeParam = time
            ?.let { value -> "?time=${URLEncoder.encode(UtcDateTimes.format(value), StandardCharsets.UTF_8)}" }
            ?: ""

        return webClientBuilder.build()
            .get()
            .uri(URI.create("$ballisticsServiceUrl/api/angular-motion/satellites$timeParam"))
            .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 getAngularMotionFlightLine(satelliteId: Long, time: LocalDateTime): JsonNode =
        webClientBuilder.build()
            .get()
            .uri(URI.create("$ballisticsServiceUrl/api/angular-motion/satellites/$satelliteId/flight-line?time=${URLEncoder.encode(UtcDateTimes.format(time), StandardCharsets.UTF_8)}"))
            .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 calculateAngularMotion(request: AngularMotionCalculationRequestDTO): JsonNode =
        webClientBuilder.build()
            .post()
            .uri("$ballisticsServiceUrl/api/angular-motion/calculate")
            .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()

    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)
    }
}
