package space.nstart.pcp.slots_service.controller

import jakarta.validation.Valid
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ResponseStatusException
import space.nstart.pcp.pcp_types_lib.dto.ballistics.InitialConditionsDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotCalculationSummaryDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteCreateDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupCreateDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupUpdateDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteObservationProfileDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSlotProfileDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteUpdateDTO
import space.nstart.pcp.slots_service.service.GroupStateService
import space.nstart.pcp.slots_service.service.BallisticsService
import space.nstart.pcp.slots_service.service.SatelliteCatalogService
import space.nstart.pcp.slots_service.service.SatellitePdcmService
import space.nstart.pcp.slots_service.service.SlotService
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException
import java.time.format.ResolverStyle

@RestController
@RequestMapping("/api/catalog")
class CatalogRestController(
    private val satelliteCatalogService: SatelliteCatalogService,
    private val slotService: SlotService,
    private val groupStateService: GroupStateService,
    private val satellitePdcmService: SatellitePdcmService,
    private val ballisticsService: BallisticsService
) {
    private val sendInitialConditionsTimeFormatter =
        DateTimeFormatter.ofPattern("dd.MM.uuuu HH:mm:ss.SSS").withResolverStyle(ResolverStyle.STRICT)

    @GetMapping("/satellites")
    fun allSatellites() = satelliteCatalogService.allSatellites()

    @GetMapping("/satellites/slot-calculation-summaries")
    fun satelliteSlotCalculationSummaries(@RequestParam satelliteIds: List<Long>) =
        runCatching { slotService.slotCalculationSummaries(satelliteIds) }
            .getOrElse { satelliteIds.distinct().map { satelliteId -> SlotCalculationSummaryDTO(satelliteId = satelliteId) } }

    @GetMapping("/satellites/{satelliteId}")
    fun satellite(@PathVariable satelliteId: Long) = satelliteCatalogService.satellite(satelliteId)

    @GetMapping("/satellites/{satelliteId}/slot-calculation-summary")
    fun satelliteSlotCalculationSummary(@PathVariable satelliteId: Long) =
        runCatching { slotService.slotCalculationSummary(satelliteId) }
            .getOrElse { SlotCalculationSummaryDTO(satelliteId = satelliteId) }

    @GetMapping("/satellites/pdcm")
    fun satellitesPdcm() = satellitePdcmService.satelliteSummaries()

    @GetMapping("/satellites/pdcm/{satelliteId}")
    fun satellitePdcm(@PathVariable satelliteId: Long) = satellitePdcmService.satelliteDetails(satelliteId)

    @PostMapping("/satellites/pdcm/{satelliteId}/send-ic")
    fun sendSatelliteInitialConditions(
        @PathVariable satelliteId: Long,
        @RequestParam(required = false) time: String?
    ): ResponseEntity<Void> {
        val requestedTime = parseSendInitialConditionsTime(time)
        slotService.sendSatelliteInitialConditions(satelliteId, requestedTime)
        return ResponseEntity.accepted().build()
    }

    @PostMapping("/satellites/pdcm/{satelliteId}/initial-conditions")
    fun setSatelliteInitialConditions(
        @PathVariable satelliteId: Long,
        @RequestBody request: SatelliteICDTO
    ): ResponseEntity<Void> {
        ballisticsService.receiveRv(
            SatelliteICDTO(
                satelliteId = satelliteId,
                ic = request.ic,
                movementModel = request.movementModel
            )
        )
        return ResponseEntity.accepted().build()
    }

    @GetMapping("/satellites/pdcm/{satelliteId}/initial-conditions")
    fun currentPdcmInitialConditions(@PathVariable satelliteId: Long): ResponseEntity<Any> {
        val response = ballisticsService.currentInitialConditions(satelliteId)
        return if (response == null) {
            ResponseEntity.noContent().build()
        } else {
            ResponseEntity.ok(response)
        }
    }

    private fun parseSendInitialConditionsTime(time: String?): LocalDateTime {
        val normalizedTime = time?.trim()?.takeIf { it.isNotEmpty() }
            ?: return UtcDateTimes.now()

        UtcDateTimes.parseOrNull(normalizedTime)?.let { return it }

        return try {
            LocalDateTime.parse(normalizedTime, sendInitialConditionsTimeFormatter)
        } catch (exception: DateTimeParseException) {
            throw ResponseStatusException(
                HttpStatus.BAD_REQUEST,
                "Параметр time должен быть ISO-8601 UTC/offset или legacy dd.mm.yyyy hh:MM:ss.zzz",
                exception
            )
        }
    }

    @PostMapping("/satellites")
    fun createSatellite(@Valid @RequestBody request: SatelliteCreateDTO) =
        satelliteCatalogService.createSatellite(request)

    @PutMapping("/satellites/{satelliteId}")
    fun updateSatellite(
        @PathVariable satelliteId: Long,
        @Valid @RequestBody request: SatelliteUpdateDTO
    ) = satelliteCatalogService.updateSatellite(satelliteId, request)

    @DeleteMapping("/satellites/{satelliteId}")
    fun deleteSatellite(@PathVariable satelliteId: Long): ResponseEntity<Void> {
        satelliteCatalogService.deleteSatellite(satelliteId)
        return ResponseEntity.noContent().build()
    }

    @PostMapping("/satellites/{satelliteId}/observation-profile")
    fun saveObservationProfile(
        @PathVariable satelliteId: Long,
        @RequestParam(defaultValue = "false") create: Boolean,
        @Valid @RequestBody request: SatelliteObservationProfileDTO
    ) = satelliteCatalogService.saveObservationProfile(satelliteId, request, create)

    @DeleteMapping("/satellites/{satelliteId}/observation-profile")
    fun deleteObservationProfile(@PathVariable satelliteId: Long): ResponseEntity<Void> {
        satelliteCatalogService.deleteObservationProfile(satelliteId)
        return ResponseEntity.noContent().build()
    }

    @PostMapping("/satellites/{satelliteId}/slot-profile")
    fun saveSlotProfile(
        @PathVariable satelliteId: Long,
        @RequestParam(defaultValue = "false") create: Boolean,
        @Valid @RequestBody request: SatelliteSlotProfileDTO
    ) = satelliteCatalogService.saveSlotProfile(satelliteId, request, create)

    @PostMapping("/satellites/{satelliteId}/slot-calculation")
    fun startSlotCalculation(
        @PathVariable satelliteId: Long,
        @Valid @RequestBody request: SatelliteSlotProfileDTO
    ): ResponseEntity<Void> {
        slotService.startSlotCalculation(satelliteId, request)
        return ResponseEntity.noContent().build()
    }

    @DeleteMapping("/satellites/{satelliteId}/slot-profile")
    fun deleteSlotProfile(@PathVariable satelliteId: Long): ResponseEntity<Void> {
        satelliteCatalogService.deleteSlotProfile(satelliteId)
        return ResponseEntity.noContent().build()
    }

    @GetMapping("/satellites/{satelliteId}/initial-conditions")
    fun satelliteInitialConditions(@PathVariable satelliteId: Long): ResponseEntity<Any> {
        val response = slotService.satelliteInitialConditions(satelliteId)
        return if (response == null) {
            ResponseEntity.noContent().build()
        } else {
            ResponseEntity.ok(response)
        }
    }

    @PostMapping("/satellites/{satelliteId}/initial-conditions")
    fun saveSatelliteInitialConditions(
        @PathVariable satelliteId: Long,
        @RequestParam(defaultValue = "false") create: Boolean,
        @Valid @RequestBody request: InitialConditionsDTO
    ) = slotService.saveSatelliteInitialConditions(satelliteId, request, create)

    @GetMapping("/groups")
    fun allGroups() = satelliteCatalogService.allGroups()

    @GetMapping("/groups/{groupId}")
    fun group(@PathVariable groupId: Long) = satelliteCatalogService.group(groupId)

    @GetMapping("/group-states")
    fun allGroupStates() = groupStateService.groupSummaries()

    @GetMapping("/group-states/{groupId}")
    fun groupState(
        @PathVariable groupId: Long,
        @RequestParam(required = false) time: LocalDateTime?
    ) = groupStateService.groupState(groupId, time)

    @PostMapping("/groups")
    fun createGroup(@Valid @RequestBody request: SatelliteGroupCreateDTO) =
        satelliteCatalogService.createGroup(request)

    @PutMapping("/groups/{groupId}")
    fun updateGroup(
        @PathVariable groupId: Long,
        @Valid @RequestBody request: SatelliteGroupUpdateDTO
    ) = satelliteCatalogService.updateGroup(groupId, request)

    @DeleteMapping("/groups/{groupId}")
    fun deleteGroup(@PathVariable groupId: Long): ResponseEntity<Void> {
        satelliteCatalogService.deleteGroup(groupId)
        return ResponseEntity.noContent().build()
    }
}
