package space.nstart.pcp.pcp_coverage_scheme_service.client

import org.slf4j.LoggerFactory
import org.springframework.beans.factory.ObjectProvider
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.MediaType
import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.netty.http.client.HttpClient
import space.nstart.pcp.pcp_coverage_scheme_service.configuration.CoverageSchemeProperties
import space.nstart.pcp.pcp_coverage_scheme_service.configuration.CustomErrorException
import space.nstart.pcp.pcp_types_lib.dto.ballistics.ExactTimePositionRequestDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.FlightLineDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.ObjViewRequestDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.PointViewParamDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SquareViewParamDTO
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.Duration
import java.time.LocalDateTime

@Service
class BallisticsClient(
    webClientBuilderProvider: ObjectProvider<WebClient.Builder>,
    private val properties: CoverageSchemeProperties
) : CoverageBallisticsClient {


    private val logger = LoggerFactory.getLogger(this::class.java)
    private val webClientBuilder: WebClient.Builder = webClientBuilderProvider.ifAvailable ?: WebClient.builder()

    private fun client(): WebClient =
        webClientBuilder
            .baseUrl(properties.ballisticsServiceUrl)
            .clientConnector(
                ReactorClientHttpConnector(
                    HttpClient.create().responseTimeout(Duration.ofSeconds(properties.webClientResponseTimeoutSeconds))
                )
            )
            .codecs { codecs -> codecs.defaultCodecs().maxInMemorySize(properties.webClientInMemorySizeMb * 1024 * 1024) }
            .build()

    override fun mplSquare(req: ObjViewRequestDTO): List<SquareViewParamDTO> =
        client()
            .post()
            .uri("/api/obj-view/mpl-square")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(req)
            .retrieve()
            .bodyToFlux(SquareViewParamDTO::class.java)
            .collectList()
            .block()
            ?: emptyList()

    override fun mplPoint(req: ObjViewRequestDTO): List<PointViewParamDTO> =
        client()
            .post()
            .uri("/api/obj-view/mpl-point")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(req)
            .retrieve()
            .bodyToFlux(PointViewParamDTO::class.java)
            .collectList()
            .doOnError { error ->
                logger.warn(
                    "Failed to fetch mpl point from ballistics-service: satellites={}, interval=[{} - {}], error={}",
                    req.satellites,
                    req.timeStart,
                    req.timeStop,
                    error.message
                )
            }
            .onErrorMap { error ->
                CustomErrorException("Не удалось получить mpl-point из баллистики: ${error.message}")
            }
            .block()
            ?: emptyList()

    override fun flightLine(satelliteId: Long, timeStart: LocalDateTime, timeStop: LocalDateTime): List<FlightLineDTO> =
        client()
            .get()
            .uri { uriBuilder ->
                uriBuilder
                    .path("/api/satellites/{satelliteId}/flight-line")
                    .queryParam("time_start", UtcDateTimes.format(timeStart))
                    .queryParam("time_stop", UtcDateTimes.format(timeStop))
                    .build(satelliteId)
            }
            .retrieve()
            .bodyToFlux(FlightLineDTO::class.java)
            .collectList()
            .doOnError { error ->
                logger.warn(
                    "Failed to fetch flight line from ballistics-service: satelliteId={}, interval=[{} - {}], error={}",
                    satelliteId,
                    timeStart,
                    timeStop,
                    error.message
                )
            }
            .onErrorMap { error ->
                CustomErrorException("Не удалось получить трассу полета для КА $satelliteId: ${error.message}")
            }
            .block()
            ?: emptyList()

    override fun exactTime(satelliteId: Long, request: ExactTimePositionRequestDTO): List<OrbPointDTO> =
        client()
            .post()
            .uri("/api/satellites/{satelliteId}/extract-time", satelliteId)
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(request)
            .retrieve()
            .bodyToFlux(OrbPointDTO::class.java)
            .collectList()
            .doOnError { error ->
                logger.warn(
                    "Failed to fetch orbital points from ballistics-service: satelliteId={}, interval=[{} - {}], stepMs={}, error={}",
                    satelliteId,
                    request.time,
                    request.timeStop,
                    request.stepMs,
                    error.message
                )
            }
            .onErrorMap { error ->
                CustomErrorException("Не удалось получить точки орбиты для КА $satelliteId: ${error.message}")
            }
            .block()
            ?: emptyList()
}
