package space.nstart.pcp.pcp_satellites_service.service

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.test.context.bean.override.mockito.MockitoSpyBean
import org.mockito.Mockito.doThrow
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 java.time.LocalDateTime
import kotlin.test.assertEquals
import kotlin.test.assertTrue

@SpringBootTest
class SatelliteModePersistenceServiceTest {

    @Autowired
    private lateinit var satelliteModePersistenceService: SatelliteModePersistenceService

    @Autowired
    private lateinit var satelliteModeQueryService: SatelliteModeQueryService

    @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 complexPlanPersistenceService: ComplexPlanPersistenceService

    @Autowired
    private lateinit var complexPlanRunRepository: ComplexPlanRunRepository

    @MockitoSpyBean
    private lateinit var satelliteModeRepositorySpy: SatelliteModeRepository

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

    @Test
    fun `deleteModesForInterval removes only intersecting satellite modes and relations`() {
        val intervalStart = LocalDateTime.of(2026, 3, 26, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 26, 12, 0)

        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L, 56756L),
            intervalStart = intervalStart.minusHours(2),
            intervalEnd = intervalEnd.plusHours(2),
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 100,
                        time = intervalStart.plusMinutes(10),
                        timeStop = intervalStart.plusMinutes(20),
                        roll = 5.0,
                        contourWKT = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
                        source = SatelliteModeSource.SLOTS,
                        bookedSlotIds = listOf(1001L),
                        cellNums = listOf(42L)
                    )
                ),
                56756L to listOf(
                    SurveyMode(
                        revolution = 200,
                        time = intervalEnd.plusHours(1),
                        timeStop = intervalEnd.plusHours(2),
                        roll = 7.0,
                        contourWKT = "POLYGON ((2 0, 3 0, 3 1, 2 1, 2 0))",
                        source = SatelliteModeSource.COMPLAN,
                        bookedSlotIds = listOf(2001L),
                        cellNums = listOf(99L)
                    )
                )
            )
        )

        satelliteModePersistenceService.deleteModesForInterval(listOf(22L), intervalStart, intervalEnd)

        val remaining = satelliteModeRepository.findAll()
        assertEquals(1, remaining.size)
        assertEquals(56756L, remaining.single().satelliteId)
        assertEquals(1, satelliteModeSnapshotRepository.count())
        assertEquals(1, satelliteModeBookedSlotRepository.findAll().size)
        assertEquals(1, satelliteModeCellRepository.findAll().size)
    }

    @Test
    fun `saveModesForInterval persists source booked slots and cell numbers`() {
        val intervalStart = LocalDateTime.of(2026, 3, 26, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 26, 12, 0)

        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 300,
                        time = intervalStart.plusMinutes(5),
                        timeStop = intervalStart.plusMinutes(25),
                        roll = 8.0,
                        contourWKT = "POLYGON ((0 0, 2 0, 2 1, 0 1, 0 0))",
                        source = SatelliteModeSource.SLOTS,
                        bookedSlotIds = listOf(101L, 102L),
                        cellNums = listOf(42L, 43L)
                    )
                )
            )
        )

        val modes = satelliteModeQueryService.findModesByInterval(listOf(22L), intervalStart, intervalEnd)
        assertEquals(1, modes.size)
        assertEquals("SLOTS", modes.single().source)
        assertEquals(listOf(101L, 102L), modes.single().bookedSlotIds)
        assertEquals(listOf(42L, 43L), modes.single().cellNums)
        assertEquals(-1L, modes.single().cellNum)
        assertTrue(satelliteModeCellRepository.findAll().size == 2)
    }

    @Test
    fun `findModesByInterval reads active snapshot by satellite and filters returned modes by interval`() {
        val snapshotStart = LocalDateTime.of(2026, 3, 26, 8, 0)
        val snapshotEnd = LocalDateTime.of(2026, 3, 26, 14, 0)
        val requestedStart = LocalDateTime.of(2026, 3, 26, 10, 0)
        val requestedEnd = LocalDateTime.of(2026, 3, 26, 11, 0)

        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = snapshotStart,
            intervalEnd = snapshotEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 301,
                        time = snapshotStart.plusMinutes(5),
                        timeStop = snapshotStart.plusMinutes(25),
                        roll = 7.0,
                        contourWKT = "POLYGON ((0 0, 2 0, 2 1, 0 1, 0 0))",
                        source = SatelliteModeSource.SLOTS
                    ),
                    SurveyMode(
                        revolution = 302,
                        time = requestedStart.plusMinutes(10),
                        timeStop = requestedStart.plusMinutes(20),
                        roll = 8.0,
                        contourWKT = "POLYGON ((0 0, 2 0, 2 1, 0 1, 0 0))",
                        source = SatelliteModeSource.COMPLAN
                    )
                )
            )
        )

        val modes = satelliteModeQueryService.findModesByInterval(listOf(22L), requestedStart, requestedEnd)

        assertEquals(1, modes.size)
        assertEquals(302L, modes.single().revolution)
        assertEquals("COMPLAN", modes.single().source)
    }

    @Test
    fun `saveModesForInterval stores complex plan run relation`() {
        val intervalStart = LocalDateTime.of(2026, 3, 26, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 26, 12, 0)
        val run = complexPlanRunPersistenceService.createRun(
            ComplexPlanRunCreateDTO(
                intervalStart = intervalStart,
                intervalEnd = intervalEnd,
                satelliteIds = listOf(22L),
                requestedBy = "tester",
                requestPayload = "{}"
            )
        )

        complexPlanPersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 400,
                        time = intervalStart.plusMinutes(5),
                        timeStop = intervalStart.plusMinutes(15),
                        roll = 8.0,
                        contourWKT = "POLYGON ((0 0, 2 0, 2 1, 0 1, 0 0))",
                        source = SatelliteModeSource.COMPLAN,
                        cellNums = listOf(42L)
                    )
                )
            ),
            complexPlanRunId = run.id
        )

        val linkedModes = satelliteModeRepository.findAllByComplexPlanRun_IdOrderByStartTimeAsc(run.id!!)
        assertEquals(1, linkedModes.size)
        assertEquals(run.id, linkedModes.single().complexPlanRun?.id)
        assertEquals(run.id, linkedModes.single().snapshot?.complexPlanRun?.id)
    }

    @Test
    fun `replaceModesForInterval creates new active snapshot and keeps old history`() {
        val intervalStart = LocalDateTime.of(2026, 3, 26, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 26, 12, 0)

        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 100,
                        time = intervalStart.plusMinutes(1),
                        timeStop = intervalStart.plusMinutes(10),
                        roll = 5.0,
                        source = SatelliteModeSource.SLOTS,
                        bookedSlotIds = listOf(101L),
                        cellNums = listOf(42L)
                    )
                )
            )
        )

        complexPlanPersistenceService.replaceModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 200,
                        time = intervalStart.plusMinutes(20),
                        timeStop = intervalStart.plusMinutes(30),
                        roll = 6.0,
                        source = SatelliteModeSource.COMPLAN,
                        bookedSlotIds = listOf(202L),
                        cellNums = listOf(43L, 44L)
                    )
                )
            )
        )

        val modes = satelliteModeQueryService.findModesByInterval(listOf(22L), intervalStart, intervalEnd)
        assertEquals(1, modes.size)
        assertEquals("COMPLAN", modes.single().source)
        assertEquals(listOf(202L), modes.single().bookedSlotIds)
        assertEquals(listOf(43L, 44L), modes.single().cellNums)
        val snapshots = satelliteModeSnapshotRepository.findAll().sortedBy { it.id }
        assertEquals(2, snapshots.size)
        assertTrue(snapshots.last().isActive)
        assertTrue(!snapshots.first().isActive)
    }

    @Test
    fun `saveModesForInterval keeps only one active snapshot per satellite across different intervals`() {
        val firstStart = LocalDateTime.of(2026, 3, 26, 10, 0)
        val firstEnd = LocalDateTime.of(2026, 3, 26, 12, 0)
        val secondStart = LocalDateTime.of(2026, 3, 27, 10, 0)
        val secondEnd = LocalDateTime.of(2026, 3, 27, 12, 0)

        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = firstStart,
            intervalEnd = firstEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 401,
                        time = firstStart.plusMinutes(5),
                        timeStop = firstStart.plusMinutes(15),
                        roll = 4.0,
                        source = SatelliteModeSource.SLOTS
                    )
                )
            )
        )

        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = secondStart,
            intervalEnd = secondEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 402,
                        time = secondStart.plusMinutes(5),
                        timeStop = secondStart.plusMinutes(15),
                        roll = 5.0,
                        source = SatelliteModeSource.COMPLAN
                    )
                )
            )
        )

        val snapshots = satelliteModeSnapshotRepository.findAll().filter { it.satelliteId == 22L }.sortedBy { it.createdAt }
        assertEquals(2, snapshots.size)
        assertEquals(1, snapshots.count { it.isActive })
        assertTrue(!snapshots.first().isActive)
        assertTrue(snapshots.last().isActive)
    }

    @Test
    fun `replaceModesForInterval rolls back and keeps old result when save fails`() {
        val intervalStart = LocalDateTime.of(2026, 3, 26, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 26, 12, 0)

        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 300,
                        time = intervalStart.plusMinutes(1),
                        timeStop = intervalStart.plusMinutes(10),
                        roll = 5.0,
                        source = SatelliteModeSource.SLOTS,
                        bookedSlotIds = listOf(301L),
                        cellNums = listOf(42L)
                    )
                )
            )
        )

        kotlin.test.assertFailsWith<Exception> {
            complexPlanPersistenceService.replaceModesForInterval(
                satelliteIds = listOf(22L),
                intervalStart = intervalStart,
                intervalEnd = intervalEnd,
                surveysBySatelliteId = mapOf(
                    22L to listOf(
                        SurveyMode(
                            revolution = 301,
                            time = intervalStart.plusMinutes(20),
                            timeStop = intervalStart.plusMinutes(30),
                            roll = 6.0,
                            source = SatelliteModeSource.COMPLAN,
                            bookedSlotIds = listOf(302L),
                            cellNums = listOf(43L)
                        )
                    )
                ),
                complexPlanRunId = Long.MAX_VALUE
            )
        }

        val modes = satelliteModeQueryService.findModesByInterval(listOf(22L), intervalStart, intervalEnd)
        assertEquals(1, modes.size)
        assertEquals("SLOTS", modes.single().source)
        assertEquals(listOf(301L), modes.single().bookedSlotIds)
        assertEquals(listOf(42L), modes.single().cellNums)
        assertEquals(1, satelliteModeSnapshotRepository.count())
        assertTrue(satelliteModeSnapshotRepository.findAll().single().isActive)
    }

    @Test
    fun `saveModesForInterval creates stage4 snapshot and keeps it active`() {
        val intervalStart = LocalDateTime.of(2026, 3, 28, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 28, 12, 0)

        val result = satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 601,
                        time = intervalStart.plusMinutes(10),
                        timeStop = intervalStart.plusMinutes(20),
                        roll = 5.0,
                        source = SatelliteModeSource.COMPLAN,
                        contourWKT = "POLYGON ((0 0, 2 0, 2 1, 0 1, 0 0))"
                    )
                )
            )
        )

        assertEquals(1, result.snapshotIds.size)
        assertEquals(result.snapshotIds, result.activeSnapshotIds)
        val snapshot = satelliteModeSnapshotRepository.findById(result.snapshotIds.single()).orElseThrow()
        assertEquals(22L, snapshot.satelliteId)
        assertTrue(snapshot.isActive)
        assertEquals(snapshot.id, satelliteModeRepository.findAll().single().snapshot?.id)
    }

    @Test
    fun `saveModesForInterval persists MANUAL source`() {
        val intervalStart = LocalDateTime.of(2026, 3, 26, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 26, 12, 0)

        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 450,
                        time = intervalStart.plusMinutes(10),
                        timeStop = intervalStart.plusMinutes(20),
                        roll = 6.0,
                        source = SatelliteModeSource.MANUAL,
                        contourWKT = "POLYGON ((0 0, 2 0, 2 1, 0 1, 0 0))",
                        cellNums = listOf(42L)
                    )
                )
            )
        )

        val savedMode = satelliteModeRepository.findAll().single()
        assertEquals(SatelliteModeSource.MANUAL, savedMode.source)
    }

    @Test
    fun `saveModesForInterval derives coordinates from contour for COMPLAN and MIXED`() {
        val intervalStart = LocalDateTime.of(2026, 3, 26, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 26, 12, 0)

        satelliteModePersistenceService.saveModesForInterval(
            satelliteIds = listOf(22L),
            intervalStart = intervalStart,
            intervalEnd = intervalEnd,
            surveysBySatelliteId = mapOf(
                22L to listOf(
                    SurveyMode(
                        revolution = 500,
                        time = intervalStart.plusMinutes(5),
                        timeStop = intervalStart.plusMinutes(15),
                        roll = 4.0,
                        source = SatelliteModeSource.COMPLAN,
                        contourWKT = "POLYGON ((0 0, 2 0, 2 1, 0 1, 0 0))",
                        cellNums = listOf(42L)
                    ),
                    SurveyMode(
                        revolution = 501,
                        time = intervalStart.plusMinutes(20),
                        timeStop = intervalStart.plusMinutes(30),
                        roll = 4.0,
                        source = SatelliteModeSource.MIXED,
                        contourWKT = "POLYGON ((10 10, 12 10, 12 11, 10 11, 10 10))",
                        cellNums = listOf(43L)
                    )
                )
            )
        )

        val savedModes = satelliteModeRepository.findAll().sortedBy { it.startTime }
        assertEquals(2, savedModes.size)
        assertEquals(0.5, savedModes[0].lat)
        assertEquals(1.0, savedModes[0].longitude)
        assertEquals(10.5, savedModes[1].lat)
        assertEquals(11.0, savedModes[1].longitude)
    }
}
