package space.nstart.pcp.slots_service.service

import org.locationtech.jts.geom.Polygon
import org.springframework.beans.factory.ObjectProvider
import org.springframework.beans.factory.annotation.Autowired
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.RevolutionSign
import space.nstart.pcp.pcp_types_lib.dto.coverage.CoverageSchemeCalculateRequestDTO
import space.nstart.pcp.pcp_types_lib.dto.coverage.CoverageSchemeResponseDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteInfoDTO
import space.nstart.pcp.pcp_types_lib.utils.wkt.WKTParser
import space.nstart.pcp.slots_service.configuration.CustomErrorException
import space.nstart.pcp.slots_service.czml.CZMLColor
import space.nstart.pcp.slots_service.czml.CZMLEntity
import space.nstart.pcp.slots_service.czml.CZMLMaterial
import space.nstart.pcp.slots_service.czml.CZMLPolygon
import space.nstart.pcp.slots_service.czml.CZMLPosition
import tools.jackson.databind.ObjectMapper
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime

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

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

    @Autowired
    private lateinit var earthService: EarthService

    @Autowired
    private lateinit var complexMissionService: ComplexMissionService

    fun requestCoverageScheme(
        requestId: String,
        timeStart: LocalDateTime,
        timeStop: LocalDateTime,
        satelliteIds: List<Long>,
        rollStepDegrees: Double?,
        minimumTechnologyOverlap: Double?,
        strictContinuousCoverage: Boolean?,
        allowPartialCoverage: Boolean?,
        orientationToleranceDegrees: Double?,
        includeDebugInfo: Boolean?
    ): CoverageSchemeUiResponseDTO {
        if (satelliteIds.isEmpty()) {
            throw CustomErrorException("Для расчета схемы покрытия выберите хотя бы один спутник")
        }

        val request = earthService.reqcells(requestId)?.request
            ?: throw CustomErrorException("Заявка $requestId не найдена")

        val response = webClientBuilder.build()
            .post()
            .uri("$url/api/coverage-schemes/calculate")
            .bodyValue(
                CoverageSchemeCalculateRequestDTO(
                    polygonWkt = request.contour,
                    satelliteIds = satelliteIds,
                    timeStart = timeStart,
                    timeEnd = timeStop,
                    rollStepDegrees = rollStepDegrees,
                    minimumTechnologyOverlap = minimumTechnologyOverlap,
                    strictContinuousCoverage = strictContinuousCoverage ?: true,
                    allowPartialCoverage = allowPartialCoverage,
                    orientationToleranceDegrees = orientationToleranceDegrees,
                    includeDebugInfo = includeDebugInfo ?: true
                )
            )
            .retrieve()
            .onStatus({ status -> status.isError }) { httpResponse ->
                httpResponse.bodyToMono(String::class.java)
                    .defaultIfEmpty("error")
                    .flatMap { body -> Mono.error(CustomErrorException(extractErrorMessage(body))) }
            }
            .bodyToMono(CoverageSchemeResponseDTO::class.java)
            .block()
            ?: CoverageSchemeResponseDTO()

        val rows = response.coverageScheme
            .mapIndexed { index, slot ->
                val sequence = index + 1
                CoverageSchemeModeRowDTO(
                    entityId = buildCoverageModeEntityId(requestId, sequence, slot.satelliteId),
                    sequence = sequence,
                    satelliteId = slot.satelliteId,
                    tn = slot.tn,
                    tk = slot.tk,
                    roll = slot.roll,
                    revolutionSign = slot.revolutionSign,
                    contour = slot.contour
                )
            }

        val satelliteById = rows
            .map { it.satelliteId }
            .distinct()
            .associateWith { satelliteId -> complexMissionService.satellite(satelliteId).block() }

        val entities = buildList {
            add(CZMLEntity("document", "req_cov_scheme_$requestId", "1.1"))
            rows.mapNotNullTo(this) { row ->
                row.toCzmlEntity(satelliteById[row.satelliteId])
            }
        }

        return CoverageSchemeUiResponseDTO(
            cov = entities,
            slots = rows
        )
    }

    private fun CoverageSchemeModeRowDTO.toCzmlEntity(satellite: SatelliteInfoDTO?): CZMLEntity? {
        val geometry = runCatching { wktParser.parseWKT(contour) }.getOrNull()
        if (geometry !is Polygon) {
            return null
        }

        val positions = mutableListOf<Double>()
        geometry.coordinates.forEach { coordinate ->
            positions.add(coordinate.x)
            positions.add(coordinate.y)
            positions.add(0.0)
        }

        return CZMLEntity(
            id = entityId,
            name = "Режим схемы покрытия",
            version = "1.1",
            description = buildString {
                append("Последовательность ${sequence}<br>")
                append("КА №${satelliteId}<br>")
                append("ДМВ ${tn}<br>")
                append("Длительность ${java.time.Duration.between(tn, tk).seconds}<br>")
                append("Крен ${roll ?: "-"}<br>")
                append("Ветвь ${revolutionSign ?: "-"}")
                if (!selectionReason.isNullOrBlank()) {
                    append("<br>Причина выбора ${selectionReason}")
                }
            },
            polygon = CZMLPolygon(
                positions = CZMLPosition(cartographicDegrees = positions),
                outlineWidth = 2.0,
                fill = true,
                outlineColor = satellite?.toOutlineColor() ?: CZMLColor(30, 144, 255, 255),
                material = satellite?.toMaterial() ?: CZMLMaterial(30, 144, 255, 150)
            )
        )
    }

    private fun SatelliteInfoDTO.toOutlineColor() =
        CZMLColor(red.toInt(), green.toInt(), blue.toInt(), 255)

    private fun SatelliteInfoDTO.toMaterial() =
        CZMLMaterial(red.toInt(), green.toInt(), blue.toInt(), 150)

    private fun buildCoverageModeEntityId(requestId: String, sequence: Int, satelliteId: Long) =
        "coverage_mode_${requestId}_${satelliteId}_$sequence"

    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)
    }
}

class CoverageSchemeUiResponseDTO(
    val cov: List<CZMLEntity> = listOf(),
    val slots: List<CoverageSchemeModeRowDTO> = listOf()
)

class CoverageSchemeModeRowDTO(
    val entityId: String = "",
    val sequence: Int = 0,
    val satelliteId: Long = 0,
    val tn: LocalDateTime = UtcDateTimes.now(),
    val tk: LocalDateTime = UtcDateTimes.now(),
    val roll: Double? = null,
    val revolutionSign: RevolutionSign? = null,
    val contour: String = "",
    val selectionReason: String? = null
)
