package space.nstart.pcp.pcp_satellites_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 org.springframework.web.reactive.function.client.bodyToFlux
import org.springframework.web.util.UriComponentsBuilder
import reactor.core.publisher.Mono
import space.nstart.pcp.pcp_satellites_service.configuration.CustomErrorException
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.BookedSlotDTO
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.BookingRequestDTO
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotDTO
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SurveySlotDTO
import tools.jackson.databind.ObjectMapper
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime

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

    @Value("\${settings.slots-service:slots-service}")
    val url = ""

    private fun client() = webClientBuilder.baseUrl(url).build()

    fun slots(wkt : String, tn : LocalDateTime, tk : LocalDateTime, sign : RevolutionSign?, cov : Boolean?, sats : List<Long>) =
        (
            if (sign == null) {
                client()
                    .get()
                    .uri(
                        UriComponentsBuilder
                            .fromUriString("/api/slots/poly-cover")
                            .queryParam("wkt", wkt)
                            .queryParam("timeStart", UtcDateTimes.format(tn))
                            .queryParam("timeStop", UtcDateTimes.format(tk))
                            .queryParam("cov", cov)
                            .apply {
                                sats.forEach { queryParam("satellites", it) }
                            }
                            .build()
                            .toUriString()
                    )
            } else {
                client()
                    .get()
                    .uri(
                        "/api/slots/poly-cover?wkt={wkt}&timeStart={timeStart}&timeStop={timeStop}&revSign={revSign}&cov={cov}",
                        wkt,
                        UtcDateTimes.format(tn),
                        UtcDateTimes.format(tk),
                        sign,
                        cov
                    )
            }
        )
            .retrieve()
            .onStatus({ status -> status.isError }) { response ->
                response.bodyToMono(String::class.java)
                    .defaultIfEmpty("error")
                    .flatMap { body -> Mono.error(CustomErrorException(extractErrorMessage(body))) }
            }
            .bodyToFlux<SlotDTO>()


    fun plan(id : Long, tn : LocalDateTime, tk : LocalDateTime) =
        client()
            .get()
            .uri(
                "/api/slots/mission/satellite?satellite_id={satelliteId}&timeStart={timeStart}&timeStop={timeStop}",
                id,
                UtcDateTimes.format(tn),
                UtcDateTimes.format(tk)
            )
            .retrieve()
            .onStatus({ status -> status.isError }) { response ->
                response.bodyToMono(String::class.java)
                    .defaultIfEmpty("error")
                    .flatMap { body -> Mono.error(CustomErrorException(extractErrorMessage(body))) }
            }
            .bodyToFlux< SurveySlotDTO>()


    fun bookReq(req: BookingRequestDTO): List<BookedSlotDTO> =
        client()
            .post()
            .uri("/api/slots/booking/request")
            .bodyValue(req)
            .retrieve()
            .bodyToFlux(BookedSlotDTO::class.java)
            .collectList()
            .block() ?: emptyList()

    fun cancelReq(requestId: String): Int =
        client()
            .delete()
            .uri("/api/slots/booking/request?requestId={requestId}", requestId)
            .retrieve()
            .onStatus({ status -> status.isError }) { response ->
                response.bodyToMono(String::class.java)
                    .defaultIfEmpty("error")
                    .flatMap { body -> Mono.error(CustomErrorException(extractErrorMessage(body))) }
            }
            .bodyToMono(Int::class.java)
            .block() ?: 0

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