package space.nstart.pcp.routepassportconsumer.service

import org.springframework.stereotype.Service
import space.nstart.pcp.pcp_types_lib.dto.routes.AngleRangeDto
import space.nstart.pcp.pcp_types_lib.dto.routes.ParsedRoutePassportMessage
import space.nstart.pcp.pcp_types_lib.dto.routes.RawFeaturePassportJsonDto
import space.nstart.pcp.pcp_types_lib.dto.routes.RawPassportEnvelopeDto
import space.nstart.pcp.pcp_types_lib.dto.routes.RoutePassportDto
import space.nstart.pcp.pcp_types_lib.dto.routes.RoutePassportMessageMetadata
import space.nstart.pcp.routepassportconsumer.exception.RoutePassportContractException
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import tools.jackson.core.JacksonException
import tools.jackson.databind.ObjectMapper
import java.math.BigDecimal
import java.nio.charset.StandardCharsets
import java.util.UUID

/**
 * Сервис, который парсит JSON паспорта маршрута в итоговый DTO.
 */
@Service
class RoutePassportJsonParserService(

    /** Jackson ObjectMapper, который выполняет базовую десериализацию JSON. */
    private val objectMapper: ObjectMapper,

    /** Converter GeoJSON geometry входного паспорта маршрута в WKT. */
    private val routeGeometryConverter: RouteGeometryConverter,
) {

    /**
     * Парсит входной JSON и возвращает готовый DTO.
     *
     * @param json исходное Kafka-сообщение envelope-формата с полезной нагрузкой в поле `data`.
     * @return DTO с уже преобразованными полями.
     */
    fun parse(json: String): ParsedRoutePassportMessage {
        val rawEnvelope: RawPassportEnvelopeDto = try {
            objectMapper.readValue(json, RawPassportEnvelopeDto::class.java)
        } catch (exception: JacksonException) {
            throw RoutePassportContractException("Некорректный JSON contract паспорта маршрута", exception)
        }
        val routePassportDto = rawEnvelope.data.toRoutePassportDto()

        return ParsedRoutePassportMessage(
            metadata = RoutePassportMessageMetadata(
                traceId = rawEnvelope.traceId,
                correlationId = rawEnvelope.correlationId,
            ),
            routePassportDto = routePassportDto,
        )
    }

    /**
     * Преобразует feature из envelope-сообщения в бизнес DTO.
     */
    private fun RawFeaturePassportJsonDto.toRoutePassportDto(): RoutePassportDto {
        val routePassportDto = RoutePassportDto(
            routeId = routeId ?: UUID.nameUUIDFromBytes(id.toByteArray(StandardCharsets.UTF_8)),
            kaShort = properties.missionId,
            kaFull = properties.platform,
            routeNameFull = id,
            routeNameShort = collection,
            orbitNumber = properties.orbitNumber.toLong(),
            orbitState = properties.orbitState,
            intervalBegin = UtcDateTimes.parse(properties.intervalBegin),
            intervalEnd = UtcDateTimes.parse(properties.intervalEnd),
            rollAngle = properties.rollAngle.toAngleRangeDto(fieldName = "rollAngle"),
            visirAngle = properties.visirAngle.toAngleRangeDto(fieldName = "visirAngle"),
            resolutionRange = properties.resolutionRange.toBigDecimal(),
            resolutionAzimuth = properties.resolutionAzimuth.toBigDecimal(),
            polarisation = properties.polarisation,
            processingLevel = properties.processingLevel,
            geometry = routeGeometryConverter.convert(geometry),
        )

        return routePassportDto
    }

    /**
     * Преобразует список чисел в DTO диапазона.
     *
     * @receiver список значений диапазона.
     * @param fieldName имя поля для текста ошибки.
     * @return DTO диапазона с минимальным и максимальным значением.
     */
    private fun List<BigDecimal>.toAngleRangeDto(fieldName: String): AngleRangeDto {
        if (this.isEmpty()) {
            throw RoutePassportContractException("Поле $fieldName не должно быть пустым")
        }

        /** Минимальное значение диапазона. */
        val minValue: BigDecimal = this.minOrNull()
            ?: throw RoutePassportContractException("Не удалось определить минимум для поля $fieldName")

        /** Максимальное значение диапазона. */
        val maxValue: BigDecimal = this.maxOrNull()
            ?: throw RoutePassportContractException("Не удалось определить максимум для поля $fieldName")

        /** DTO диапазона после вычисления минимума и максимума. */
        val angleRangeDto: AngleRangeDto = AngleRangeDto(
            min = minValue,
            max = maxValue,
        )

        return angleRangeDto
    }
}
