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 reactor.core.publisher.Mono
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteCreateDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupCreateDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupUpdateDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteObservationProfileDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSlotProfileDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSummaryDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteUpdateDTO
import space.nstart.pcp.slots_service.configuration.CustomErrorException
import tools.jackson.databind.ObjectMapper

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

    @Value("\${settings.satellite-catalog-service:\${settings.complex-mission-service:pcp-satellite-catalog-service}}")
    val satelliteCatalogUrl = ""

    fun allSatellites(): List<SatelliteSummaryDTO> =
        webClientBuilder.build()
            .get()
            .uri("$satelliteCatalogUrl/api/satellites")
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .bodyToFlux(SatelliteSummaryDTO::class.java)
            .collectList()
            .block()
            ?: emptyList()

    fun satellite(id: Long): SatelliteDTO =
        webClientBuilder.build()
            .get()
            .uri("$satelliteCatalogUrl/api/satellites/$id")
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .bodyToMono(SatelliteDTO::class.java)
            .block() ?: throw CustomErrorException("Не удалось получить спутник $id: пустой ответ")

    fun createSatellite(request: SatelliteCreateDTO): SatelliteDTO =
        webClientBuilder.build()
            .post()
            .uri("$satelliteCatalogUrl/api/satellites")
            .bodyValue(request)
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .bodyToMono(SatelliteDTO::class.java)
            .block() ?: throw CustomErrorException("Не удалось создать спутник: пустой ответ")

    fun updateSatellite(id: Long, request: SatelliteUpdateDTO): SatelliteDTO =
        webClientBuilder.build()
            .put()
            .uri("$satelliteCatalogUrl/api/satellites/$id")
            .bodyValue(request)
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .bodyToMono(SatelliteDTO::class.java)
            .block() ?: throw CustomErrorException("Не удалось обновить спутник $id: пустой ответ")

    fun deleteSatellite(id: Long) {
        webClientBuilder.build()
            .delete()
            .uri("$satelliteCatalogUrl/api/satellites/$id")
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .toBodilessEntity()
            .block()
    }

    fun saveObservationProfile(id: Long, request: SatelliteObservationProfileDTO, create: Boolean): SatelliteObservationProfileDTO {
        val methodSpec = if (create) webClientBuilder.build().post() else webClientBuilder.build().put()
        return methodSpec
            .uri("$satelliteCatalogUrl/api/satellites/$id/observation-profile")
            .bodyValue(request)
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .bodyToMono(SatelliteObservationProfileDTO::class.java)
            .block() ?: throw CustomErrorException("Не удалось сохранить observation-profile для КА $id")
    }

    fun deleteObservationProfile(id: Long) {
        webClientBuilder.build()
            .delete()
            .uri("$satelliteCatalogUrl/api/satellites/$id/observation-profile")
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .toBodilessEntity()
            .block()
    }

    fun saveSlotProfile(id: Long, request: SatelliteSlotProfileDTO, create: Boolean): SatelliteSlotProfileDTO {
        val methodSpec = if (create) webClientBuilder.build().post() else webClientBuilder.build().put()
        return methodSpec
            .uri("$satelliteCatalogUrl/api/satellites/$id/slot-profile")
            .bodyValue(request)
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .bodyToMono(SatelliteSlotProfileDTO::class.java)
            .block() ?: throw CustomErrorException("Не удалось сохранить slot-profile для КА $id")
    }

    fun deleteSlotProfile(id: Long) {
        webClientBuilder.build()
            .delete()
            .uri("$satelliteCatalogUrl/api/satellites/$id/slot-profile")
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .toBodilessEntity()
            .block()
    }

    fun allGroups(): List<SatelliteGroupDTO> =
        webClientBuilder.build()
            .get()
            .uri("$satelliteCatalogUrl/api/satellite-groups")
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .bodyToFlux(SatelliteGroupDTO::class.java)
            .collectList()
            .block()
            ?: emptyList()

    fun group(id: Long): SatelliteGroupDTO =
        webClientBuilder.build()
            .get()
            .uri("$satelliteCatalogUrl/api/satellite-groups/$id")
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .bodyToMono(SatelliteGroupDTO::class.java)
            .block() ?: throw CustomErrorException("Не удалось получить группировку $id: пустой ответ")

    fun createGroup(request: SatelliteGroupCreateDTO): SatelliteGroupDTO =
        webClientBuilder.build()
            .post()
            .uri("$satelliteCatalogUrl/api/satellite-groups")
            .bodyValue(request)
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .bodyToMono(SatelliteGroupDTO::class.java)
            .block() ?: throw CustomErrorException("Не удалось создать группировку: пустой ответ")

    fun updateGroup(id: Long, request: SatelliteGroupUpdateDTO): SatelliteGroupDTO =
        webClientBuilder.build()
            .put()
            .uri("$satelliteCatalogUrl/api/satellite-groups/$id")
            .bodyValue(request)
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .bodyToMono(SatelliteGroupDTO::class.java)
            .block() ?: throw CustomErrorException("Не удалось обновить группировку $id: пустой ответ")

    fun deleteGroup(id: Long) {
        webClientBuilder.build()
            .delete()
            .uri("$satelliteCatalogUrl/api/satellite-groups/$id")
            .retrieve()
            .onStatus({ status -> status.isError }, ::mapError)
            .toBodilessEntity()
            .block()
    }

    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.hasNonNull("error") -> node["error"].asText()
                node.hasNonNull("message") -> node["message"].asText()
                else -> text
            }
        }.getOrDefault(text)
    }
}
