package space.nstart.pcp.slots_service.service

import org.slf4j.LoggerFactory
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.TLEExtensionDTO
import space.nstart.pcp.slots_service.configuration.CustomErrorException
import tools.jackson.databind.ObjectMapper
import kotlin.system.measureTimeMillis

@Service
class TleMonitoringService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>) {
    private val webClientBuilder: WebClient.Builder = webClientBuilderProvider.ifAvailable ?: WebClient.builder()
    private val logger = LoggerFactory.getLogger(this::class.java)

    @Value("\${settings.tle-monitoring-service:http://tle-monitoring-service:8080}")
    private val tleMonitoringUrl: String = ""

    fun allTles(): List<TLEExtensionDTO> {
        val baseUrl = tleMonitoringUrl.trimEnd('/')
        logger.info("TLE monitoring client all TLE request started: baseUrl={}", baseUrl)
        return runCatching {
            var records = emptyList<TLEExtensionDTO>()
            val durationMs = measureTimeMillis {
                records = webClientBuilder.build()
                    .get()
                    .uri("$baseUrl/v1/api/tle")
                    .retrieve()
                    .onStatus({ status -> status.isError }, ::mapError)
                    .bodyToFlux(TLEExtensionDTO::class.java)
                    .collectList()
                    .block()
                    ?: emptyList()
            }
            logger.info(
                "TLE monitoring client all TLE request completed: records={}, durationMs={}",
                records.size,
                durationMs
            )
            records
        }.getOrElse { exception ->
            logger.warn("TLE monitoring client all TLE request failed: {}", exception.message)
            throw exception
        }
    }

    fun satelliteTles(satelliteId: Long): List<TLEExtensionDTO> {
        val baseUrl = tleMonitoringUrl.trimEnd('/')
        logger.info(
            "TLE monitoring client satellite TLE request started: satelliteId={}, baseUrl={}",
            satelliteId,
            baseUrl
        )
        return runCatching {
            var records = emptyList<TLEExtensionDTO>()
            val durationMs = measureTimeMillis {
                records = webClientBuilder.build()
                    .get()
                    .uri("$baseUrl/v1/api/tle/satellite/$satelliteId")
                    .retrieve()
                    .onStatus({ status -> status.isError }, ::mapError)
                    .bodyToFlux(TLEExtensionDTO::class.java)
                    .collectList()
                    .block()
                    ?: emptyList()
            }
            logger.info(
                "TLE monitoring client satellite TLE request completed: satelliteId={}, records={}, durationMs={}",
                satelliteId,
                records.size,
                durationMs
            )
            records
        }.getOrElse { exception ->
            logger.warn(
                "TLE monitoring client satellite TLE request failed: satelliteId={}, error={}",
                satelliteId,
                exception.message
            )
            throw exception
        }
    }

    private fun mapError(response: org.springframework.web.reactive.function.client.ClientResponse): Mono<Throwable> =
        response.bodyToMono(String::class.java)
            .defaultIfEmpty("error")
            .flatMap { body -> Mono.error(CustomErrorException(extractErrorMessage(body))) }

    private fun extractErrorMessage(body: String): String {
        val text = body.trim()
        if (text.isEmpty()) return "error"

        return runCatching {
            val node = ObjectMapper().readTree(text)
            when {
                node.has("message") -> node.get("message").asText()
                node.has("error") -> node.get("error").asText()
                else -> text
            }
        }.getOrDefault(text)
    }
}
