package space.nstart.pcp.slots_service.service

import ballistics.types.InitialConditions
import ballistics.types.OrbitalPoint
import ballistics.utils.fromDateTime
import ballistics.utils.math.Vector3D
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import space.nstart.pcp.pcp_types_lib.dto.ballistics.InitialConditionsDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
import space.nstart.pcp.slots_service.configuration.CustomValidationException
import space.nstart.pcp.slots_service.entity.SatelliteIcEntity
import space.nstart.pcp.slots_service.repository.SatelliteIcRepository

@Service
class SatelliteIcService(
    private val satelliteIcRepository: SatelliteIcRepository,
    private val satelliteCatalogClient: SatelliteCatalogClient
) {

    @Transactional(readOnly = true)
    fun all(): List<SatelliteICDTO> =
        satelliteIcRepository.findAllByOrderBySatelliteIdAsc().map { it.toDto() }

    @Transactional(readOnly = true)
    fun bySatelliteId(satelliteId: Long): SatelliteICDTO =
        requireSatelliteIc(satelliteId).toDto()

    @Transactional(readOnly = true)
    fun initialConditions(satelliteId: Long): InitialConditions =
        requireSatelliteIc(satelliteId).toModel()

    @Transactional
    fun create(request: SatelliteICDTO): SatelliteICDTO {
        requireSatelliteExists(request.satelliteId)
        if (satelliteIcRepository.existsById(request.satelliteId)) {
            throw CustomValidationException("Начальные условия КА ${request.satelliteId} уже зарегистрированы")
        }
        return satelliteIcRepository.save(request.toEntity()).toDto()
    }

    @Transactional
    fun update(satelliteId: Long, request: InitialConditionsDTO): SatelliteICDTO {
        requireSatelliteExists(satelliteId)
        if (!satelliteIcRepository.existsById(satelliteId)) {
            throw CustomValidationException("Начальные условия КА $satelliteId не зарегистрированы")
        }
        return satelliteIcRepository.save(request.toEntity(satelliteId)).toDto()
    }

    @Transactional
    fun delete(satelliteId: Long) {
        if (!satelliteIcRepository.existsById(satelliteId)) {
            throw CustomValidationException("Начальные условия КА $satelliteId не зарегистрированы")
        }
        satelliteIcRepository.deleteById(satelliteId)
    }

    private fun requireSatelliteIc(satelliteId: Long): SatelliteIcEntity =
        satelliteIcRepository.findById(satelliteId).orElseThrow {
            CustomValidationException("Начальные условия КА $satelliteId не зарегистрированы")
        }

    private fun requireSatelliteExists(satelliteId: Long) {
        satelliteCatalogClient.satellite(satelliteId)
    }

    private fun SatelliteIcEntity.toDto(): SatelliteICDTO =
        SatelliteICDTO(
            satelliteId = satelliteId,
            ic = InitialConditionsDTO(
                orbPoint = OrbPointDTO(
                    time = orbTime,
                    revolution = orbRevolution,
                    vx = vx,
                    vy = vy,
                    vz = vz,
                    x = x,
                    y = y,
                    z = z
                ),
                sBall = sBall,
                f81 = f81
            )
        )

    private fun SatelliteIcEntity.toModel(): InitialConditions =
        InitialConditions(
            point = OrbitalPoint(
                fromDateTime(orbTime),
                orbRevolution.toInt(),
                Vector3D(x, y, z),
                Vector3D(vx, vy, vz)
            ),
            sBall,
            f81
        )

    private fun SatelliteICDTO.toEntity(): SatelliteIcEntity = ic.toEntity(satelliteId)

    private fun InitialConditionsDTO.toEntity(satelliteId: Long): SatelliteIcEntity =
        SatelliteIcEntity(
            satelliteId = satelliteId,
            orbTime = orbPoint.time,
            orbRevolution = orbPoint.revolution,
            x = orbPoint.x,
            y = orbPoint.y,
            z = orbPoint.z,
            vx = orbPoint.vx,
            vy = orbPoint.vy,
            vz = orbPoint.vz,
            sBall = sBall,
            f81 = f81
        )
}
