package space.nstart.pcp.pcp_satellites_service.controller

import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.web.reactive.function.client.WebClient
import space.nstart.pcp.pcp_satellites_service.dto.ComplexPlanRunCreateDTO
import space.nstart.pcp.pcp_satellites_service.entity.SatelliteModeSource
import space.nstart.pcp.pcp_satellites_service.model.mode.SurveyMode
import space.nstart.pcp.pcp_satellites_service.repository.ComplexPlanRunRepository
import space.nstart.pcp.pcp_satellites_service.repository.SatelliteModeBookedSlotRepository
import space.nstart.pcp.pcp_satellites_service.repository.SatelliteModeCellRepository
import space.nstart.pcp.pcp_satellites_service.repository.SatelliteModeRepository
import space.nstart.pcp.pcp_satellites_service.repository.SatelliteModeSnapshotRepository
import space.nstart.pcp.pcp_satellites_service.service.ComplexPlanRunPersistenceService
import space.nstart.pcp.pcp_satellites_service.service.SatelliteModePersistenceService
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SatelliteModeResponseDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SatelliteModeSnapshotResponseDTO
import java.time.LocalDateTime
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

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

    @LocalServerPort
    private var port: Int = 0

    @Autowired
    private lateinit var satelliteModePersistenceService: SatelliteModePersistenceService

    @Autowired
    private lateinit var satelliteModeRepository: SatelliteModeRepository

    @Autowired
    private lateinit var satelliteModeBookedSlotRepository: SatelliteModeBookedSlotRepository

    @Autowired
    private lateinit var satelliteModeCellRepository: SatelliteModeCellRepository

    @Autowired
    private lateinit var satelliteModeSnapshotRepository: SatelliteModeSnapshotRepository

    @Autowired
    private lateinit var complexPlanRunPersistenceService: ComplexPlanRunPersistenceService

    @Autowired
    private lateinit var complexPlanRunRepository: ComplexPlanRunRepository

    @BeforeEach
    fun setUp() {
        satelliteModeBookedSlotRepository.deleteAll()
        satelliteModeCellRepository.deleteAll()
        satelliteModeRepository.deleteAll()
        satelliteModeSnapshotRepository.deleteAll()
        complexPlanRunRepository.deleteAll()
    }

    @Test
    fun `modes endpoint returns interval-filtered modes from active snapshot regardless of snapshot interval`() {
        val snapshotStart = LocalDateTime.of(2026, 3, 26, 8, 0)
        val snapshotEnd = LocalDateTime.of(2026, 3, 26, 14, 0)
        val intervalStart = LocalDateTime.of(2026, 3, 26, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 26, 12, 0)
        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = snapshotStart,
            intervalEnd = snapshotEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 100,
                        time = intervalStart.plusMinutes(1),
                        timeStop = intervalStart.plusMinutes(10),
                        roll = 5.0,
                        contourWKT = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
                        source = SatelliteModeSource.COMPLAN,
                        bookedSlotIds = listOf(777L),
                        cellNums = listOf(42L, 43L)
                    )
                )
            )
        )

        val response = WebClient.create("http://localhost:$port")
            .get()
            .uri("/api/satellites/modes?intervalStart=2026-03-26T10:00:00&intervalEnd=2026-03-26T12:00:00&satelliteId=22")
            .retrieve()
            .bodyToFlux(SatelliteModeResponseDTO::class.java)
            .collectList()
            .block()
            .orEmpty()

        assertEquals(1, response.size)
        assertEquals(22L, response.single().satelliteId)
        assertEquals("COMPLAN", response.single().source)
        assertEquals(listOf(777L), response.single().bookedSlotIds)
        assertEquals(listOf(42L, 43L), response.single().cellNums)
        assertNotNull(response.single().snapshotId)
    }

    @Test
    fun `satellite scoped modes endpoint returns interval-filtered modes from active snapshot`() {
        val snapshotStart = LocalDateTime.of(2026, 3, 27, 8, 0)
        val snapshotEnd = LocalDateTime.of(2026, 3, 27, 14, 0)
        val intervalStart = LocalDateTime.of(2026, 3, 27, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 27, 12, 0)
        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = snapshotStart,
            intervalEnd = snapshotEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 101,
                        time = intervalStart.plusMinutes(2),
                        timeStop = intervalStart.plusMinutes(8),
                        roll = 6.0,
                        contourWKT = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
                        source = SatelliteModeSource.MANUAL,
                        bookedSlotIds = listOf(778L),
                        cellNums = listOf(44L)
                    )
                )
            )
        )

        val response = WebClient.create("http://localhost:$port")
            .get()
            .uri("/api/satellites/22/modes?intervalStart=2026-03-27T10:00:00&intervalEnd=2026-03-27T12:00:00")
            .retrieve()
            .bodyToFlux(SatelliteModeResponseDTO::class.java)
            .collectList()
            .block()
            .orEmpty()

        assertEquals(1, response.size)
        assertEquals("MANUAL", response.single().source)
        assertEquals(listOf(778L), response.single().bookedSlotIds)
    }

    @Test
    fun `satellite scoped modes endpoint returns modes for requested runId`() {
        val intervalStart = LocalDateTime.of(2026, 3, 27, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 27, 12, 0)
        val firstRun = complexPlanRunPersistenceService.createRun(
            ComplexPlanRunCreateDTO(
                intervalStart = intervalStart,
                intervalEnd = intervalEnd,
                satelliteIds = listOf(22L),
                requestedBy = "tester",
                requestPayload = "{}"
            )
        )
        val secondRun = complexPlanRunPersistenceService.createRun(
            ComplexPlanRunCreateDTO(
                intervalStart = intervalStart,
                intervalEnd = intervalEnd,
                satelliteIds = listOf(22L),
                requestedBy = "tester",
                requestPayload = "{}"
            )
        )

        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 201,
                        time = intervalStart.plusMinutes(2),
                        timeStop = intervalStart.plusMinutes(8),
                        roll = 6.0,
                        contourWKT = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
                        source = SatelliteModeSource.COMPLAN
                    )
                )
            ),
            complexPlanRun = firstRun
        )
        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 202,
                        time = intervalStart.plusMinutes(3),
                        timeStop = intervalStart.plusMinutes(9),
                        roll = 7.0,
                        contourWKT = "POLYGON ((0 0, 2 0, 2 1, 0 1, 0 0))",
                        source = SatelliteModeSource.MANUAL
                    )
                )
            ),
            complexPlanRun = secondRun
        )

        val response = WebClient.create("http://localhost:$port")
            .get()
            .uri(
                "/api/satellites/22/modes" +
                    "?intervalStart=2026-03-27T10:00:00" +
                    "&intervalEnd=2026-03-27T12:00:00" +
                    "&runId=${firstRun.id}"
            )
            .retrieve()
            .bodyToFlux(SatelliteModeResponseDTO::class.java)
            .collectList()
            .block()
            .orEmpty()

        assertEquals(1, response.size)
        assertEquals(201L, response.single().revolution)
        assertEquals("COMPLAN", response.single().source)
    }

    @Test
    fun `active snapshot endpoint ignores interval and resolves by satellite only`() {
        val snapshotStart = LocalDateTime.of(2026, 3, 30, 8, 0)
        val snapshotEnd = LocalDateTime.of(2026, 3, 30, 14, 0)
        val queryStart = LocalDateTime.of(2026, 3, 30, 10, 0)
        val queryEnd = LocalDateTime.of(2026, 3, 30, 11, 0)
        val snapshotId = satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = snapshotStart,
            intervalEnd = snapshotEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 301,
                        time = queryStart.plusMinutes(5),
                        timeStop = queryStart.plusMinutes(15),
                        roll = 5.0,
                        contourWKT = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
                        source = SatelliteModeSource.COMPLAN
                    )
                )
            )
        ).snapshotIds.single()

        val response = WebClient.create("http://localhost:$port")
            .get()
            .uri("/api/satellites/22/snapshots/active?intervalStart=$queryStart&intervalEnd=$queryEnd")
            .retrieve()
            .bodyToMono(SatelliteModeSnapshotResponseDTO::class.java)
            .block()!!

        assertEquals(snapshotId, response.id)
        assertEquals(22L, response.satelliteId)
        assertEquals(snapshotStart, response.intervalStart)
        assertEquals(snapshotEnd, response.intervalEnd)
    }

    @Test
    fun `snapshot endpoints return metadata and modes for explicit snapshot`() {
        val intervalStart = LocalDateTime.of(2026, 3, 28, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 28, 12, 0)
        val snapshotId = satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 102,
                        time = intervalStart.plusMinutes(3),
                        timeStop = intervalStart.plusMinutes(9),
                        roll = 6.0,
                        contourWKT = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
                        source = SatelliteModeSource.COMPLAN
                    )
                )
            )
        ).snapshotIds.single()

        val snapshot = WebClient.create("http://localhost:$port")
            .get()
            .uri("/api/satellites/snapshots/$snapshotId")
            .retrieve()
            .bodyToMono(SatelliteModeSnapshotResponseDTO::class.java)
            .block()!!
        val modes = WebClient.create("http://localhost:$port")
            .get()
            .uri("/api/satellites/snapshots/$snapshotId/modes")
            .retrieve()
            .bodyToFlux(SatelliteModeResponseDTO::class.java)
            .collectList()
            .block()
            .orEmpty()

        assertEquals(snapshotId, snapshot.id)
        assertEquals(22L, snapshot.satelliteId)
        assertEquals(intervalStart, snapshot.intervalStart)
        assertEquals(intervalEnd, snapshot.intervalEnd)
        assertEquals(true, snapshot.isActive)
        assertEquals(listOf(snapshotId), modes.mapNotNull { it.snapshotId }.distinct())
    }

    @Test
    fun `snapshot modes endpoint filters by requested interval`() {
        val snapshotStart = LocalDateTime.of(2026, 3, 29, 8, 0)
        val snapshotEnd = LocalDateTime.of(2026, 3, 29, 14, 0)
        val requestedStart = LocalDateTime.of(2026, 3, 29, 10, 0)
        val requestedEnd = LocalDateTime.of(2026, 3, 29, 11, 0)
        val snapshotId = satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = snapshotStart,
            intervalEnd = snapshotEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 201,
                        time = snapshotStart.plusMinutes(10),
                        timeStop = snapshotStart.plusMinutes(20),
                        roll = 5.0,
                        contourWKT = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
                        source = SatelliteModeSource.COMPLAN
                    ),
                    SurveyMode(
                        revolution = 202,
                        time = requestedStart.plusMinutes(5),
                        timeStop = requestedStart.plusMinutes(15),
                        roll = 5.0,
                        contourWKT = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
                        source = SatelliteModeSource.MANUAL
                    )
                )
            )
        ).snapshotIds.single()

        val modes = WebClient.create("http://localhost:$port")
            .get()
            .uri(
                "/api/satellites/snapshots/$snapshotId/modes?intervalStart=$requestedStart&intervalEnd=$requestedEnd"
            )
            .retrieve()
            .bodyToFlux(SatelliteModeResponseDTO::class.java)
            .collectList()
            .block()
            .orEmpty()

        assertEquals(1, modes.size)
        assertEquals(202L, modes.single().revolution)
        assertEquals(snapshotId, modes.single().snapshotId)
    }
}
