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 org.springframework.web.util.UriComponentsBuilder
import space.nstart.pcp.pcp_types_lib.dto.ballistics.ExactTimePositionRequestDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.AscNodeDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.FlightLineDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteOrbitAvailabilityDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TlePointRequestDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRevolutionsRequestDTO
import space.nstart.pcp.slots_service.dto.tlecomparison.TleParsedDTO
import space.nstart.pcp.slots_service.configuration.CustomErrorException
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.net.URI
import java.time.LocalDateTime


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

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


    fun ascNodes(id : Long, timeStart: LocalDateTime? = null, timeStop: LocalDateTime? = null) =
        webClientBuilder.build()
            .get()
            .uri(withTimeInterval("/api/satellites/$id/asc-node", timeStart, timeStop))
            .retrieve()
            .bodyToFlux(AscNodeDTO::class.java)


    fun flightLine(id : Long, timeStart: LocalDateTime? = null, timeStop: LocalDateTime? = null) =
        webClientBuilder.build()
            .get()
            .uri(withTimeInterval("/api/satellites/$id/flight-line", timeStart, timeStop))
            .retrieve()
            .bodyToFlux(FlightLineDTO::class.java)


    fun points(id : Long, timeStart: LocalDateTime? = null, timeStop: LocalDateTime? = null) =
        webClientBuilder.build()
            .get()
            .uri(withTimeInterval("/api/satellites/$id/orbit", timeStart, timeStop))
            .retrieve()
            .bodyToFlux(OrbPointDTO::class.java)

    fun orbitAvailability(): List<SatelliteOrbitAvailabilityDTO> =
        webClientBuilder.build()
            .get()
            .uri("$url/api/satellites/orbit/availability")
            .retrieve()
            .bodyToFlux(SatelliteOrbitAvailabilityDTO::class.java)
            .collectList()
            .block()
            ?: emptyList()

    fun exactTimePoint(id: Long, time: LocalDateTime): OrbPointDTO =
        webClientBuilder.build()
            .post()
            .uri("$url/api/satellites/$id/extract-time")
            .bodyValue(ExactTimePositionRequestDTO(time = time))
            .retrieve()
            .bodyToFlux(OrbPointDTO::class.java)
            .collectList()
            .block()
            ?.firstOrNull()
            ?: throw CustomErrorException("Не удалось получить положение спутника $id на время $time")

    fun receiveRv(request: SatelliteICDTO) {
        webClientBuilder.build()
            .post()
            .uri("$url/api/satellites/receive-rv")
            .bodyValue(request)
            .retrieve()
            .toBodilessEntity()
            .block()
    }

    fun currentInitialConditions(satelliteId: Long): SatelliteICDTO? =
        webClientBuilder.build()
            .get()
            .uri("$url/api/satellites/$satelliteId/initial-conditions/current")
            .exchangeToMono { response ->
                when {
                    response.statusCode().is2xxSuccessful -> response.bodyToMono(SatelliteICDTO::class.java)
                    response.statusCode().value() == 404 || response.statusCode().value() == 204 -> reactor.core.publisher.Mono.empty()
                    else -> response.createException().flatMap { reactor.core.publisher.Mono.error(it) }
                }
            }
            .block()

    fun parseTle(tle: TLEDTO): TleParsedDTO =
        webClientBuilder.build()
            .post()
            .uri("$url/api/tle/parse")
            .bodyValue(tle)
            .retrieve()
            .bodyToMono(TleParsedDTO::class.java)
            .block()
            ?: throw CustomErrorException("Не удалось разобрать TLE")

    fun tlePoint(tle: TLEDTO, time: LocalDateTime): OrbPointDTO =
        webClientBuilder.build()
            .post()
            .uri("$url/api/tle/point")
            .bodyValue(TlePointRequestDTO(tle = tle, time = time))
            .retrieve()
            .bodyToMono(OrbPointDTO::class.java)
            .block()
            ?: throw CustomErrorException("Не удалось рассчитать положение по TLE на время $time")

    fun tleRevolutions(tle: TLEDTO, days: Double): List<AscNodeDTO> =
        webClientBuilder.build()
            .post()
            .uri("$url/api/tle/revolutions")
            .bodyValue(TleRevolutionsRequestDTO(tle = tle, days = days))
            .retrieve()
            .bodyToFlux(AscNodeDTO::class.java)
            .collectList()
            .block()
            ?: emptyList()

    private fun withTimeInterval(
        path: String,
        timeStart: LocalDateTime?,
        timeStop: LocalDateTime?
    ): URI {
        val builder = UriComponentsBuilder.fromUriString("$url$path")
        timeStart?.let { builder.queryParam("time_start", UtcDateTimes.format(it)) }
        timeStop?.let { builder.queryParam("time_stop", UtcDateTimes.format(it)) }
        return builder.build().toUri()
    }
}
