package space.nstart.pcp.pcp_satellite_catalog_service.controller

import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.http.HttpStatusCode
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteBatchRequestDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteCreateDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupCreateDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupDTO
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.SatelliteSlotAngleDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSlotProfileDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSummaryDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteUpdateDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteVisualizationDTO
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SatelliteCatalogControllerTest {

    @LocalServerPort
    private var port: Int = 0

    @Test
    fun `catalog returns seeded summaries`() {
        val response = client()
            .get()
            .uri("/api/satellites")
            .retrieve()
            .bodyToFlux(SatelliteSummaryDTO::class.java)
            .collectList()
            .block()
            .orEmpty()

        assertEquals(28, response.size)
        assertEquals("KONDOR-FKA NO. 1", response.first { it.id == 56756L }.name)
        assertEquals(true, response.first { it.id == 62138L }.scanTle)
    }

    @Test
    fun `catalog returns full satellite card`() {
        val response = client()
            .get()
            .uri("/api/satellites/22")
            .retrieve()
            .bodyToMono(SatelliteDTO::class.java)
            .block()!!

        assertEquals(22L, response.id)
        assertEquals("EMISSIO-22", response.code)
        assertEquals("Emissio", response.name)
        assertEquals(20, response.slotProfile?.defaultAngles?.size)
        assertEquals(18.5, response.slotProfile?.defaultAngles?.first()?.angleBegin)
        assertEquals(21.58, response.slotProfile?.defaultAngles?.first()?.angleEnd)
        assertEquals(10L, response.slotProfile?.slotDuration)
        assertEquals(300L, response.observationProfile?.durationMaxSeconds)
    }

    @Test
    fun `catalog returns profiles, batch and seeded groups`() {
        val observationProfile = client()
            .get()
            .uri("/api/satellites/56756/observation-profile")
            .retrieve()
            .bodyToMono(SatelliteObservationProfileDTO::class.java)
            .block()!!
        val slotProfile = client()
            .get()
            .uri("/api/satellites/56756/slot-profile")
            .retrieve()
            .bodyToMono(SatelliteSlotProfileDTO::class.java)
            .block()!!
        val batch = client()
            .post()
            .uri("/api/satellites/batch")
            .bodyValue(SatelliteBatchRequestDTO(ids = listOf(56756L, 62138L)))
            .retrieve()
            .bodyToFlux(SatelliteDTO::class.java)
            .collectList()
            .block()
            .orEmpty()
        val groups = client()
            .get()
            .uri("/api/satellite-groups")
            .retrieve()
            .bodyToFlux(SatelliteGroupDTO::class.java)
            .collectList()
            .block()
            .orEmpty()

        assertEquals(1.5, observationProfile.captureAngle)
        assertNotNull(slotProfile.tnCalc)
        assertEquals(10L, slotProfile.slotDuration)
        assertEquals(10, slotProfile.defaultAngles.size)
        assertEquals(20.0, slotProfile.defaultAngles.first().angleBegin)
        assertEquals(23.0, slotProfile.defaultAngles.first().angleEnd)
        assertEquals(300L, observationProfile.durationMaxSeconds)
        assertEquals(listOf(56756L, 62138L), batch.map { it.id })
        assertEquals(listOf(56756L, 62138L), groups.first { it.name == "KONDOR" }.satelliteIds)
    }

    @Test
    fun `catalog supports satellite crud`() {
        val satelliteId = 70001L
        val created = client()
            .post()
            .uri("/api/satellites")
            .bodyValue(
                SatelliteCreateDTO(
                    id = satelliteId,
                    noradId = 90001L,
                    code = "TEST-SAT-70001",
                    name = "TestSat",
                    typeCode = "TEST",
                    active = true,
                    scanTle = true,
                    visualization = SatelliteVisualizationDTO(red = 10, green = 20, blue = 30)
                )
            )
            .retrieve()
            .bodyToMono(SatelliteDTO::class.java)
            .block()!!

        val updated = client()
            .put()
            .uri("/api/satellites/$satelliteId")
            .bodyValue(
                SatelliteUpdateDTO(
                    noradId = 90002L,
                    code = "TEST-SAT-70001-UPD",
                    name = "TestSatUpdated",
                    typeCode = "TEST-UPD",
                    active = false,
                    scanTle = false,
                    visualization = SatelliteVisualizationDTO(red = 40, green = 50, blue = 60)
                )
            )
            .retrieve()
            .bodyToMono(SatelliteDTO::class.java)
            .block()!!

        delete("/api/satellites/$satelliteId")

        assertEquals(satelliteId, created.id)
        assertEquals("TEST-SAT-70001", created.code)
        assertEquals(90002L, updated.noradId)
        assertEquals("TestSatUpdated", updated.name)
        assertEquals(40, updated.visualization.red.toInt())
        assertEquals(400, status("GET", "/api/satellites/$satelliteId").value())
    }

    @Test
    fun `catalog supports profile crud`() {
        val satelliteId = 70002L
        client()
            .post()
            .uri("/api/satellites")
            .bodyValue(
                SatelliteCreateDTO(
                    id = satelliteId,
                    code = "TEST-SAT-70002",
                    name = "ProfileSat",
                    typeCode = "TEST",
                    visualization = SatelliteVisualizationDTO(red = 1, green = 2, blue = 3)
                )
            )
            .retrieve()
            .bodyToMono(SatelliteDTO::class.java)
            .block()!!

        val createdObservation = client()
            .post()
            .uri("/api/satellites/$satelliteId/observation-profile")
            .bodyValue(
                SatelliteObservationProfileDTO(
                    captureAngle = 2.5,
                    sunAngleMin = -15.0,
                    durationMinSeconds = 12,
                    durationMaxSeconds = 120,
                    mmiSeconds = 15,
                    dailyMaxDurationSeconds = 1800,
                    revolutionMaxDurationSeconds = 180
                )
            )
            .retrieve()
            .bodyToMono(SatelliteObservationProfileDTO::class.java)
            .block()!!

        val updatedObservation = client()
            .put()
            .uri("/api/satellites/$satelliteId/observation-profile")
            .bodyValue(createdObservation.copy(durationMaxSeconds = 240))
            .retrieve()
            .bodyToMono(SatelliteObservationProfileDTO::class.java)
            .block()!!

        val createdSlot = client()
            .post()
            .uri("/api/satellites/$satelliteId/slot-profile")
            .bodyValue(
                SatelliteSlotProfileDTO(
                    cycleRevs = 10,
                    slotDuration = 15,
                    durationCalcDays = 5,
                    maxChainLength = 2,
                    defaultAngles = listOf(
                        SatelliteSlotAngleDTO(angleBegin = 10.0, angleEnd = 20.0),
                        SatelliteSlotAngleDTO(angleBegin = 20.0, angleEnd = 30.0)
                    )
                )
            )
            .retrieve()
            .bodyToMono(SatelliteSlotProfileDTO::class.java)
            .block()!!

        val updatedSlot = client()
            .put()
            .uri("/api/satellites/$satelliteId/slot-profile")
            .bodyValue(
                createdSlot.copy(
                    cycleRevs = 11,
                    slotDuration = 17,
                    defaultAngles = listOf(SatelliteSlotAngleDTO(angleBegin = 30.0, angleEnd = 40.0))
                )
            )
            .retrieve()
            .bodyToMono(SatelliteSlotProfileDTO::class.java)
            .block()!!

        delete("/api/satellites/$satelliteId/observation-profile")
        delete("/api/satellites/$satelliteId/slot-profile")

        assertEquals(2.5, createdObservation.captureAngle)
        assertEquals(240L, updatedObservation.durationMaxSeconds)
        assertEquals(2, createdSlot.defaultAngles.size)
        assertEquals(15L, createdSlot.slotDuration)
        assertEquals(11L, updatedSlot.cycleRevs)
        assertEquals(17L, updatedSlot.slotDuration)
        assertEquals(30.0, updatedSlot.defaultAngles.single().angleBegin)
        assertEquals(400, status("GET", "/api/satellites/$satelliteId/observation-profile").value())
        assertEquals(400, status("GET", "/api/satellites/$satelliteId/slot-profile").value())

        delete("/api/satellites/$satelliteId")
    }

    @Test
    fun `catalog supports group crud`() {
        val satelliteId = 70003L
        client()
            .post()
            .uri("/api/satellites")
            .bodyValue(
                SatelliteCreateDTO(
                    id = satelliteId,
                    code = "TEST-SAT-70003",
                    name = "GroupSat",
                    typeCode = "TEST",
                    visualization = SatelliteVisualizationDTO(red = 7, green = 8, blue = 9)
                )
            )
            .retrieve()
            .bodyToMono(SatelliteDTO::class.java)
            .block()!!

        val createdGroup = client()
            .post()
            .uri("/api/satellite-groups")
            .bodyValue(
                SatelliteGroupCreateDTO(
                    name = "TEST-GROUP",
                    satelliteIds = listOf(satelliteId, 56756L)
                )
            )
            .retrieve()
            .bodyToMono(SatelliteGroupDTO::class.java)
            .block()!!

        val updatedGroup = client()
            .put()
            .uri("/api/satellite-groups/${createdGroup.id}")
            .bodyValue(
                SatelliteGroupUpdateDTO(
                    name = "TEST-GROUP-UPD",
                    satelliteIds = listOf(satelliteId)
                )
            )
            .retrieve()
            .bodyToMono(SatelliteGroupDTO::class.java)
            .block()!!

        delete("/api/satellite-groups/${createdGroup.id}")
        delete("/api/satellites/$satelliteId")

        assertEquals("TEST-GROUP", createdGroup.name)
        assertEquals(listOf(56756L, satelliteId), createdGroup.satelliteIds)
        assertEquals("TEST-GROUP-UPD", updatedGroup.name)
        assertEquals(listOf(satelliteId), updatedGroup.satelliteIds)
        assertEquals(400, status("GET", "/api/satellite-groups/${createdGroup.id}").value())
    }

    private fun delete(path: String) {
        client()
            .delete()
            .uri(path)
            .retrieve()
            .toBodilessEntity()
            .block()
    }

    private fun status(method: String, path: String): HttpStatusCode =
        client()
            .method(org.springframework.http.HttpMethod.valueOf(method))
            .uri(path)
            .exchangeToMono { response -> Mono.just(response.statusCode()) }
            .block()!!

    private fun client(): WebClient = WebClient.create("http://localhost:$port")
}
