package space.nstart.pcp.slots_service.controller

import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
import space.nstart.pcp.slots_service.configuration.CustomValidationException
import space.nstart.pcp.slots_service.dto.tlecomparison.TleCompareRequestDTO
import space.nstart.pcp.slots_service.dto.tlecomparison.TleCompareResponseDTO
import space.nstart.pcp.slots_service.dto.tlecomparison.TleCompareRowDTO
import space.nstart.pcp.slots_service.dto.tlecomparison.TleParsedDTO
import space.nstart.pcp.slots_service.dto.tlecomparison.TlePositionDTO
import space.nstart.pcp.slots_service.dto.tlecomparison.TlePositionDeltaDTO
import space.nstart.pcp.slots_service.dto.tlecomparison.TleTextRequestDTO
import space.nstart.pcp.slots_service.service.BallisticsService
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.Duration
import java.time.LocalDateTime
import java.util.Locale
import kotlin.math.abs
import kotlin.math.sqrt

@RestController
@RequestMapping("/api/tle-comparison")
class TleComparisonController(
    private val ballisticsService: BallisticsService
) {

    @PostMapping("/parse")
    fun parse(@RequestBody request: TleTextRequestDTO): TleParsedDTO =
        ballisticsService.parseTle(parseTleText(request.tle))

    @PostMapping("/compare")
    fun compare(@RequestBody request: TleCompareRequestDTO): TleCompareResponseDTO {
        val firstTle = parseTleText(request.first)
        val secondTle = parseTleText(request.second)
        val firstParsed = ballisticsService.parseTle(firstTle)
        val secondParsed = ballisticsService.parseTle(secondTle)
        val calculationTime = request.time
            ?: throw CustomValidationException("Задайте время сравнения TLE")
        val firstEpoch = firstParsed.time
            ?: throw CustomValidationException("Не удалось определить эпоху первого TLE")
        val secondEpoch = secondParsed.time
            ?: throw CustomValidationException("Не удалось определить эпоху второго TLE")
        val maxEpoch = maxOf(firstEpoch, secondEpoch)
        if (calculationTime < maxEpoch) {
            throw CustomValidationException("Время сравнения должно быть не раньше максимальной эпохи TLE: $maxEpoch")
        }

        val firstPoint = ballisticsService.tlePoint(firstTle, calculationTime).toPosition()
        val secondPoint = ballisticsService.tlePoint(secondTle, calculationTime).toPosition()
        val delta = TlePositionDeltaDTO(
            dx = secondPoint.x - firstPoint.x,
            dy = secondPoint.y - firstPoint.y,
            dz = secondPoint.z - firstPoint.z,
            norm = distance(secondPoint.x - firstPoint.x, secondPoint.y - firstPoint.y, secondPoint.z - firstPoint.z)
        )

        return TleCompareResponseDTO(
            time = calculationTime,
            firstEpoch = firstEpoch,
            secondEpoch = secondEpoch,
            first = firstPoint,
            second = secondPoint,
            radiusVectorDelta = delta,
            rows = buildRows(firstPoint, secondPoint, delta)
        )
    }

    private fun parseTleText(raw: String): TLEDTO {
        val lines = raw.lines()
            .map { it.trim() }
            .filter { it.isNotBlank() }

        if (lines.isEmpty()) {
            throw CustomValidationException("Введите TLE")
        }

        val firstIndex = lines.indexOfFirst { it.startsWith("1 ") }
        val secondIndex = lines.indexOfFirst { it.startsWith("2 ") }
        if (firstIndex < 0 || secondIndex < 0) {
            throw CustomValidationException("TLE должен содержать строки 1 и 2")
        }

        val first = lines[firstIndex]
        val second = lines[secondIndex]
        val header = lines.take(firstIndex)
            .firstOrNull { !it.startsWith("1 ") && !it.startsWith("2 ") }

        if (first.length < 69 || second.length < 69) {
            throw CustomValidationException("Строки TLE должны содержать не менее 69 символов")
        }

        return TLEDTO(
            header = header,
            first = first.take(69),
            second = second.take(69)
        )
    }

    private fun buildRows(
        first: TlePositionDTO,
        second: TlePositionDTO,
        delta: TlePositionDeltaDTO
    ): List<TleCompareRowDTO> = listOf(
        timeRow("time", "Время расчета", first.time, second.time),
        textRow("revolution", "Виток", first.revolution.toString(), second.revolution.toString()),
        numberRow("x", "X радиус-вектора, м", first.x, second.x),
        numberRow("y", "Y радиус-вектора, м", first.y, second.y),
        numberRow("z", "Z радиус-вектора, м", first.z, second.z),
        numberRow("radiusNorm", "Модуль радиус-вектора, м", first.radiusNorm, second.radiusNorm),
        numberRow("vx", "Vx, м/с", first.vx, second.vx),
        numberRow("vy", "Vy, м/с", first.vy, second.vy),
        numberRow("vz", "Vz, м/с", first.vz, second.vz),
        numberRow("velocityNorm", "Модуль скорости, м/с", first.velocityNorm, second.velocityNorm),
        TleCompareRowDTO("dx", "ΔX = X2 - X1, м", first = null, second = null, delta = delta.dx.formatSignedNumber()),
        TleCompareRowDTO("dy", "ΔY = Y2 - Y1, м", first = null, second = null, delta = delta.dy.formatSignedNumber()),
        TleCompareRowDTO("dz", "ΔZ = Z2 - Z1, м", first = null, second = null, delta = delta.dz.formatSignedNumber()),
        TleCompareRowDTO("radiusDeltaNorm", "|Δr|, м", first = null, second = null, delta = delta.norm.formatNumber())
    )

    private fun OrbPointDTO.toPosition(): TlePositionDTO = TlePositionDTO(
        time = time,
        revolution = revolution,
        x = x,
        y = y,
        z = z,
        vx = vx,
        vy = vy,
        vz = vz,
        radiusNorm = distance(x, y, z),
        velocityNorm = distance(vx, vy, vz)
    )

    private fun textRow(code: String, name: String, first: String?, second: String?): TleCompareRowDTO =
        TleCompareRowDTO(
            code = code,
            name = name,
            first = first,
            second = second,
            delta = if (first != null && second != null && first != second) "изменено" else null
        )

    private fun timeRow(code: String, name: String, first: LocalDateTime?, second: LocalDateTime?): TleCompareRowDTO {
        val delta = if (first != null && second != null) {
            val seconds = Duration.between(first, second).seconds
            formatSecondsDelta(seconds)
        } else {
            null
        }
        return TleCompareRowDTO(code, name, first?.let(UtcDateTimes::format), second?.let(UtcDateTimes::format), delta)
    }

    private fun numberRow(code: String, name: String, first: Double?, second: Double?): TleCompareRowDTO =
        TleCompareRowDTO(
            code = code,
            name = name,
            first = first?.formatNumber(),
            second = second?.formatNumber(),
            delta = if (first != null && second != null) (second - first).formatSignedNumber() else null
        )

    private fun distance(x: Double, y: Double, z: Double): Double = sqrt(x * x + y * y + z * z)

    private fun Double.formatNumber(): String = String.format(Locale.US, "%.6f", this)

    private fun Double.formatSignedNumber(): String {
        val sign = if (this > 0) "+" else ""
        return sign + String.format(Locale.US, "%.6f", this)
    }

    private fun formatSecondsDelta(seconds: Long): String {
        val sign = if (seconds > 0) "+" else if (seconds < 0) "-" else ""
        val absolute = abs(seconds)
        val hours = absolute / 3600
        val minutes = (absolute % 3600) / 60
        val secs = absolute % 60
        return "$sign${hours}ч ${minutes}м ${secs}с"
    }
}
