package space.nstart.pcp_tgu_service.integration.api

/** Вызывает внешнее API видимости и возвращает raw payload без потери неизвестных полей. */
import space.nstart.pcp_tgu_service.config.ExternalApiProperties
import space.nstart.pcp_tgu_service.config.PlanningProperties
import space.nstart.pcp_tgu_service.domain.PlatformCatalogItem
import space.nstart.pcp_tgu_service.domain.SpacecraftPoints
import space.nstart.pcp_tgu_service.service.PlatformService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.WebClientException
import org.springframework.web.reactive.function.client.WebClientResponseException
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes

/**
 * Клиент внешнего API с окнами видимости КА.
 */
@Component
class ExternalPointsClient(
    externalApiProperties: ExternalApiProperties,
    webClientBuilder: WebClient.Builder,
    private val platformService: PlatformService,
    private val planningProperties: PlanningProperties,
    private val visibilityPayloadParser: VisibilityPayloadParser
) {

    private val log = LoggerFactory.getLogger(javaClass)

    private val webClient: WebClient = webClientBuilder
        .baseUrl(externalApiProperties.baseUrl)
        .build()

    /**
     * Загружает окна видимости по всем КА из классификатора,
     * которые разрешены для работы TGU.
     */
    fun fetchAllSpacecraftPoints(): List<SpacecraftPoints> {
        val timeStart = UtcDateTimes.now().truncatedTo(ChronoUnit.MINUTES)
        val timeStop = timeStart.plusDays(planningProperties.externalPointsLookaheadDays)

        return platformService.loadAllowedPlatforms()
            .mapNotNull { platform ->
                val noradId = requireNotNull(platform.noradId)
                fetchSpacecraftPointsSafely(
                    platform = platform,
                    satelliteId = noradId,
                    timeStart = timeStart,
                    timeStop = timeStop
                )
            }
    }

    /** Оборачивает загрузку по одному КА и подавляет транспортные ошибки только для него. */
    private fun fetchSpacecraftPointsSafely(
        platform: PlatformCatalogItem,
        satelliteId: Long,
        timeStart: LocalDateTime,
        timeStop: LocalDateTime
    ): SpacecraftPoints? =
        try {
            fetchSpacecraftPoints(
                satelliteId = satelliteId,
                timeStart = timeStart,
                timeStop = timeStop
            )
        } catch (ex: WebClientException) {
            logExternalApiError(platform, satelliteId, timeStart, timeStop, ex)
            null
        }

    /**
     * Загружает окна видимости по одному КА и интервалу времени.
     */
    /** Загружает окна видимости для одного КА в указанном временном интервале. */
    fun fetchSpacecraftPoints(
        satelliteId: Long,
        timeStart: LocalDateTime,
        timeStop: LocalDateTime
    ): SpacecraftPoints =
        visibilityPayloadParser.parse(
            spacecraftId = satelliteId.toString(),
            payloadJson = fetchRawVisibilityPayload(satelliteId, timeStart, timeStop)
        )

    /** Загружает полный raw body окон видимости для сохранения как source of truth. */
    fun fetchRawVisibilityPayload(
        satelliteId: Long,
        timeStart: LocalDateTime,
        timeStop: LocalDateTime
    ): String =
        webClient.get()
            .uri { uriBuilder ->
                uriBuilder
                    .path("/api/satellites/{satelliteId}/rva")
                    .queryParam("time_start", UtcDateTimes.format(timeStart))
                    .queryParam("time_stop", UtcDateTimes.format(timeStop))
                    .build(satelliteId)
            }
            .retrieve()
            .bodyToMono(String::class.java)
            .block()
            ?: throw IllegalStateException("External visibility response body is empty for satelliteId=$satelliteId")

    private fun logExternalApiError(
        platform: PlatformCatalogItem,
        satelliteId: Long,
        timeStart: LocalDateTime,
        timeStop: LocalDateTime,
        ex: WebClientException
    ) {
        if (ex is WebClientResponseException) {
            log.warn(
                "Skipping spacecraft due to external API response error. name={}, noradId={}, nsiId={}, businessKey={}, timeStart={}, timeStop={}, status={}, responseBody={}",
                platform.name,
                satelliteId,
                platform.nsiId,
                platform.businessKey,
                timeStart,
                timeStop,
                ex.statusCode.value(),
                ex.responseBodyAsString
            )
            return
        }

        log.warn(
            "Skipping spacecraft due to external API transport error. name={}, noradId={}, nsiId={}, businessKey={}, timeStart={}, timeStop={}, message={}",
            platform.name,
            satelliteId,
            platform.nsiId,
            platform.businessKey,
            timeStart,
            timeStop,
            ex.message
        )
    }
}
