package space.nstart.pcp.tle_monitoring_service.service

import ballistics.Ballistics
import ballistics.types.BallisticsError
import ballistics.types.TLE
import ballistics.utils.toDateTime
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.WebClientResponseException
import org.springframework.web.reactive.function.client.bodyToMono
import org.springframework.web.util.UriComponentsBuilder
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
import space.nstart.pcp.pcp_types_lib.message.KafkaMessage
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import space.nstart.pcp.pcp_types_lib.message.PcpKafkaEvent
import space.nstart.pcp.tle_monitoring_service.configuration.CustomErrorException
import space.nstart.pcp.tle_monitoring_service.entity.TLEEntity
import space.nstart.pcp.tle_monitoring_service.message.KafkaProducer
import space.nstart.pcp.tle_monitoring_service.repository.TLERepository
import java.time.Clock
import java.time.Duration
import java.time.LocalDateTime
import java.util.UUID
import kotlin.math.PI
import kotlin.math.abs
import kotlin.text.split


@Service
class CelesTrakService(
    private val webClient: WebClient,
    private val pollingSettingsService: TlePollingSettingsService,
    private val clock: Clock = Clock.systemUTC(),
) {
    private val logger = LoggerFactory.getLogger(this::class.java)
    private val objectMapper = jacksonObjectMapper()

    @Autowired
    private lateinit var sender: KafkaProducer

    @Autowired
    private lateinit var tleRepository: TLERepository

    @Autowired
    private lateinit var complexMissionService: ComplexMissionService

    companion object {
        private const val BASE_URL = "https://celestrak.org"
        private const val USER_AGENT = "SatelliteTracker/1.0 (Spring Boot)"
    }


    @Scheduled(fixedDelay = 1000)
    fun findExpired(): Flux<TLEDTO> {
        if (!pollingSettingsService.shouldStartPolling()) {
            return Flux.empty()
        }

        val settings = pollingSettingsService.current()
        pollingSettingsService.markPollingStarted()
        logger.info("Запуск цикла опроса TLE. timeoutSeconds=${settings.timeoutSeconds}")

        return complexMissionService.getSatellites().filter { s -> s.scanTLE }.flatMap {
            sat ->
            getTleByNoradIdRaw(sat.noradId, send = true, sendForce = false)
        }
    }




    fun updateDB(){
        val bal = Ballistics()
        tleRepository.findAll().forEach { tle ->
            try{
                val dat = tle.tle.split("\r\n")
                val params = bal.getTLEParams(TLE(dat[1], dat[2]))
                tleRepository.save(tle.apply {
                    timeTle = params.time
                })

            } catch (ex : Exception){
                logger.warn("Ошибка разбора TLE ${ex.message}")
            }
        }
    }



    fun loadArray(data : String, name : String){

        val tles = data.split("\n")
        var first = true
        var s1 : String = ""
        var s2 : String
        for (s in tles){
            if (first){
                s1 = s
            } else {
                s2 = s
                val id = getNoradIdFromTLE(s1)
                mkTle(TLEDTO(name,s1, s2), id, false, false)
            }
            first = ! first
        }

    }

    fun getTleArrayByNoradIdRaw(noradId: Long, name: String): Mono<Void> {
        // Используем UriComponentsBuilder для корректного построения URL
        val uri = UriComponentsBuilder.fromUriString("https://www.space-track.org/basicspacedata/query/class/gp_history/norad_cat_id/$noradId/CREATION_DATE/%3Enow-60/format/tle")
            .build()
            .toUri()
        logger.info("Запрос архива TLE для NORAD ID: $noradId, URI: $uri")
        return webClient.get()
            .uri(uri)
            .retrieve()
            .bodyToMono<String>()
            .timeout(Duration.ofSeconds(15))
            .doOnNext { rawText ->
                loadArray(rawText, name)
            }
            .doOnError { error ->
                handleError(error, noradId, "raw-array")
            }
            .then()
    }


    fun sendTLE(satelliteId : Long, tle : TLEDTO){
        mkTle(tle, satelliteId, true, true)
    }


    /**
     * Получение TLE в сыром текстовом формате
     */
    fun getTleByNoradIdRaw(noradId: Long, send : Boolean, sendForce : Boolean): Mono<TLEDTO> {
        // Используем UriComponentsBuilder для корректного построения URL
        val uri = UriComponentsBuilder.fromUriString("$BASE_URL/NORAD/elements/gp.php")
            .queryParam("CATNR", noradId)
            .queryParam("FORMAT", "tle")
            .build()
            .toUri()
        logger.info("Запрос raw TLE для NORAD ID: $noradId, URI: $uri")
        return webClient.get()
            .uri(uri)
            .header("User-Agent", USER_AGENT)
            .retrieve()
            .bodyToMono<String>()
            .timeout(Duration.ofSeconds(15))
            .map { rawText ->
                parseRawTle(rawText, noradId)
            }
            .onErrorResume { error ->
                handleError(error, noradId, "raw")
                Mono.just(TLEDTO())
            }
            .doOnSuccess { tle ->
                if (tle != null) {
                    mkTle(tle, noradId, send, sendForce)
                }
            }
    }


    fun mkTle(tle : TLEDTO, noradId : Long, send : Boolean, sendForce : Boolean){
        val rev = getRevolutionNumberFromTLE(tle.second)
        if (rev <= 0)
            logger.error("Не удалось прочитать номер витка из строки ${tle.second} для id $noradId")
        else {


            if (tleRepository.findBySatelliteIdAndRevolution(noradId, rev).isEmpty()) {

                val bal = Ballistics()

                try {

                    val params = bal.getTLEParams(TLE(tle.first, tle.second))
                    tleRepository.save(
                        TLEEntity(
                            null,
                            noradId,
                            rev,
                            "${tle.header ?: "---"}\r\n${tle.first}\r\n${tle.second}",
                            timeTle = params.time

                        )
                    )
                    logger.info("Данные TLE для noradId $noradId сохранены в БД")
                } catch (ex: Exception) {
                    logger.warn("Ошибка разбора TLE ${ex.message}")
                    throw CustomErrorException("Ошибка разбора TLE ${ex.message}")
                }



                if (send && !sendForce) {
                    sender.send(
                        KafkaMessage(
                            type = PcpKafkaEvent.ICUpdatedEvent,
                            data = tle,
                            traceId = UUID.randomUUID().toString()
                        )
                    )
                    logger.info("Данные TLE для noradId $noradId отправлены в очередь сообщений")
                }
            } else
                logger.info("Данные TLE для noradId $noradId не изменились")


            if (sendForce) {
                logger.info("Данные TLE для noradId $noradId принудительно отправлены в очередь сообщений")
                sender.send(
                    KafkaMessage(
                        type = PcpKafkaEvent.ICUpdatedEvent,
                        data = tle,
                        traceId = UUID.randomUUID().toString()
                    )
                )
            }
        }
    }

    fun getRevolutionNumberFromTLE(line2: String): Long {
        // Номер витка находится в позициях 64-68 (индексы 63-67)
        if (line2.length >= 68) {
            val revNumberStr = line2.substring(63, 68).trim()
            return revNumberStr.toLongOrNull() ?: 0
        }
        return 0
    }

    fun getNoradIdFromTLE(line1: String): Long {
        // Номер витка находится в позициях 64-68 (индексы 63-67)
        if (line1.length >= 15) {
            val noradId = line1.substring(2, 7).trim()
            return noradId.toLongOrNull() ?: 0
        }
        return 0
    }

    /**
     * Парсинг сырого TLE текста
     */
    private fun parseRawTle(rawText: String, noradId: Long): TLEDTO {
        val lines = rawText.lines()
            .map { it.trim() }
            .filter { it.isNotBlank() }
        return when {
            lines.size >= 3 -> {
                TLEDTO(
                    lines[0],
                    lines[1],
                    lines[2]
                )
            }
            lines.size == 2 -> {
                // Если нет имени объекта
                TLEDTO(
                    "NORAD-$noradId",
                    lines[0],
                    lines[1]
                )
            }
            else -> {
                logger.warn("Некорректный TLE формат для NORAD ID: $noradId")
                throw CustomErrorException("Не удалось получить TLE: $lines")
            }
        }
    }

    private fun handleError(error: Throwable, noradId: Long, format: String) {
        when (error) {
            is WebClientResponseException -> {
                logger.error("HTTP ошибка при запросе $format TLE для NORAD ID: $noradId. " +
                        "Статус: ${error.statusCode}, Тело: ${error.responseBodyAsString}", error)
                throw CustomErrorException("HTTP ошибка при запросе $format TLE для NORAD ID: $noradId. " +
                        "Статус: ${error.statusCode}, Тело: ${error.responseBodyAsString}")
            }
            is java.net.UnknownHostException -> {
                logger.error("Не удалось разрешить хост для запроса TLE (NORAD ID: $noradId). " +
                        "Проверьте подключение к интернету.", error)
                throw CustomErrorException("Не удалось разрешить хост для запроса TLE (NORAD ID: $noradId). " +
                        "Проверьте подключение к интернету.")
            }
            is java.net.SocketTimeoutException -> {
                logger.error("Таймаут при запросе TLE для NORAD ID: $noradId", error)
                throw CustomErrorException("Таймаут при запросе TLE для NORAD ID: $noradId")
            }
            else -> {
                logger.error("Ошибка при запросе $format TLE для NORAD ID: $noradId", error)
                throw CustomErrorException("Ошибка при запросе $format TLE для NORAD ID: $noradId")
            }
        }
    }


//    https://www.space-track.org/basicspacedata/query/class/gp_history/norad_cat_id/39634/CREATION_DATE/%3Enow-60/format/tle
    fun checkCycle(id : Long){
        val bal = Ballistics()
        val tles = tleRepository.findAllBySatelliteId(id)
            .sortedBy { tle -> tle.timeTle }
        var first = true
        var vit = 0
        var tVuz = UtcDateTimes.now(clock)
        var lVuz = 0.0
        for(t in tles){
            var s = t.tle.split("\r\n")
            val r = bal.calculateOrbPoints(TLE(s[1],s[2]), 86400.0 * 10)
            if (r != BallisticsError.OK)
                throw CustomErrorException("Ошибка расчета прогноза")
            if (first){
                first = false
                vit = bal.revolutions.first().vuz.vit
                lVuz = bal.revolutions.first().lVuz * 180.0 / PI
                tVuz = toDateTime(bal.revolutions.first().vuz.t)
            } else {
                for (v in bal.revolutions){
                    val dL = abs(v.lVuz* 180.0 / PI - lVuz)
                    val dvit = v.vuz.vit-vit
                    if (dL < 0.005){
                        val dt : Long = Duration.between(tVuz, toDateTime(v.vuz.t)).seconds
                        logger.info("${tVuz} - ${toDateTime(v.vuz.t)}   сдвиг витков ${abs(vit - v.vuz.vit)} : ${dL * PI / 180.0} : ${v.vuz.r.z} : $dt : ${dt/86400}")
                    }
                }
            }
        }
    }
}
