package space.nstart.pcp.pcp_satellites_service.service

import org.junit.jupiter.api.Test
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import space.nstart.pcp.pcp_satellites_service.entity.SatelliteModeEntity
import space.nstart.pcp.pcp_satellites_service.entity.SatelliteModeSource
import space.nstart.pcp.pcp_satellites_service.entity.SatelliteModeType
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

class SatelliteModeQueryServiceTest {

    @Test
    fun `loadDayConstraintModes uses full day boundaries for single day interval`() {
        val satelliteModeRepository = mock(SatelliteModeRepository::class.java)
        val satelliteModeSnapshotRepository = mock(SatelliteModeSnapshotRepository::class.java)
        val bookedSlotRepository = mock(SatelliteModeBookedSlotRepository::class.java)
        val cellRepository = mock(SatelliteModeCellRepository::class.java)
        val service = SatelliteModeQueryService(
            satelliteModeRepository = satelliteModeRepository,
            satelliteModeSnapshotRepository = satelliteModeSnapshotRepository,
            satelliteModeBookedSlotRepository = bookedSlotRepository,
            satelliteModeCellRepository = cellRepository,
            satelliteModeMapper = SatelliteModeMapper(),
            satelliteModeSnapshotMapper = SatelliteModeSnapshotMapper()
        )

        val intervalStart = LocalDateTime.of(2026, 3, 26, 10, 15)
        val intervalEnd = LocalDateTime.of(2026, 3, 26, 11, 45)
        val dayMode = modeEntity(
            id = 1L,
            revolution = 501L,
            startTime = LocalDateTime.of(2026, 3, 26, 12, 0),
            endTime = LocalDateTime.of(2026, 3, 26, 12, 10)
        )
        val sameRevolutionOutsideDayWindow = modeEntity(
            id = 2L,
            revolution = 501L,
            startTime = LocalDateTime.of(2026, 3, 25, 23, 40),
            endTime = LocalDateTime.of(2026, 3, 25, 23, 50)
        )

        doReturn(listOf(dayMode)).`when`(satelliteModeRepository)
            .findAllBySnapshot_SatelliteIdInAndSnapshot_IsActiveTrueAndEndTimeGreaterThanAndStartTimeLessThanOrderByStartTimeAsc(
                listOf(22L),
                LocalDateTime.of(2026, 3, 26, 0, 0),
                LocalDateTime.of(2026, 3, 27, 0, 0)
            )
        doReturn(emptyList<space.nstart.pcp.pcp_satellites_service.entity.SatelliteModeBookedSlotEntity>())
            .`when`(bookedSlotRepository).findAllByMode_IdIn(org.mockito.ArgumentMatchers.anyList())
        doReturn(emptyList<space.nstart.pcp.pcp_satellites_service.entity.SatelliteModeCellEntity>())
            .`when`(cellRepository).findAllByMode_IdIn(org.mockito.ArgumentMatchers.anyList())

        val loaded = service.loadDayConstraintModes(listOf(22L), intervalStart, intervalEnd)

        assertEquals(1, loaded.getValue(22L).size)
        verify(satelliteModeRepository)
            .findAllBySnapshot_SatelliteIdInAndSnapshot_IsActiveTrueAndEndTimeGreaterThanAndStartTimeLessThanOrderByStartTimeAsc(
                listOf(22L),
                LocalDateTime.of(2026, 3, 26, 0, 0),
                LocalDateTime.of(2026, 3, 27, 0, 0)
            )
    }

    @Test
    fun `loadDayConstraintModes covers all touched days when interval crosses midnight`() {
        val satelliteModeRepository = mock(SatelliteModeRepository::class.java)
        val satelliteModeSnapshotRepository = mock(SatelliteModeSnapshotRepository::class.java)
        val bookedSlotRepository = mock(SatelliteModeBookedSlotRepository::class.java)
        val cellRepository = mock(SatelliteModeCellRepository::class.java)
        val service = SatelliteModeQueryService(
            satelliteModeRepository = satelliteModeRepository,
            satelliteModeSnapshotRepository = satelliteModeSnapshotRepository,
            satelliteModeBookedSlotRepository = bookedSlotRepository,
            satelliteModeCellRepository = cellRepository,
            satelliteModeMapper = SatelliteModeMapper(),
            satelliteModeSnapshotMapper = SatelliteModeSnapshotMapper()
        )

        val intervalStart = LocalDateTime.of(2026, 3, 26, 23, 0)
        val intervalEnd = LocalDateTime.of(2026, 3, 27, 1, 0)

        doReturn(emptyList<SatelliteModeEntity>()).`when`(satelliteModeRepository)
            .findAllBySnapshot_SatelliteIdInAndSnapshot_IsActiveTrueAndEndTimeGreaterThanAndStartTimeLessThanOrderByStartTimeAsc(
                listOf(22L),
                LocalDateTime.of(2026, 3, 26, 0, 0),
                LocalDateTime.of(2026, 3, 28, 0, 0)
            )

        service.loadDayConstraintModes(listOf(22L), intervalStart, intervalEnd)

        verify(satelliteModeRepository)
            .findAllBySnapshot_SatelliteIdInAndSnapshot_IsActiveTrueAndEndTimeGreaterThanAndStartTimeLessThanOrderByStartTimeAsc(
                listOf(22L),
                LocalDateTime.of(2026, 3, 26, 0, 0),
                LocalDateTime.of(2026, 3, 28, 0, 0)
            )
    }

    @Test
    fun `loadRevolutionConstraintModes loads same satellite revolution context outside day window`() {
        val satelliteModeRepository = mock(SatelliteModeRepository::class.java)
        val satelliteModeSnapshotRepository = mock(SatelliteModeSnapshotRepository::class.java)
        val bookedSlotRepository = mock(SatelliteModeBookedSlotRepository::class.java)
        val cellRepository = mock(SatelliteModeCellRepository::class.java)
        val service = SatelliteModeQueryService(
            satelliteModeRepository = satelliteModeRepository,
            satelliteModeSnapshotRepository = satelliteModeSnapshotRepository,
            satelliteModeBookedSlotRepository = bookedSlotRepository,
            satelliteModeCellRepository = cellRepository,
            satelliteModeMapper = SatelliteModeMapper(),
            satelliteModeSnapshotMapper = SatelliteModeSnapshotMapper()
        )

        val intervalStart = LocalDateTime.of(2026, 3, 26, 10, 15)
        val intervalEnd = LocalDateTime.of(2026, 3, 26, 11, 45)
        val dayMode = modeEntity(
            id = 1L,
            revolution = 501L,
            startTime = LocalDateTime.of(2026, 3, 26, 12, 0),
            endTime = LocalDateTime.of(2026, 3, 26, 12, 10)
        )
        val sameRevolutionOutsideDayWindow = modeEntity(
            id = 2L,
            revolution = 501L,
            startTime = LocalDateTime.of(2026, 3, 25, 23, 40),
            endTime = LocalDateTime.of(2026, 3, 25, 23, 50)
        )

        doReturn(listOf(dayMode)).`when`(satelliteModeRepository)
            .findAllBySnapshot_SatelliteIdInAndSnapshot_IsActiveTrueAndEndTimeGreaterThanAndStartTimeLessThanOrderByStartTimeAsc(
                listOf(22L),
                LocalDateTime.of(2026, 3, 26, 0, 0),
                LocalDateTime.of(2026, 3, 27, 0, 0)
            )
        doReturn(listOf(dayMode, sameRevolutionOutsideDayWindow)).`when`(satelliteModeRepository)
            .findAllBySnapshot_SatelliteIdAndSnapshot_IsActiveTrueAndRevolutionInOrderByStartTimeAsc(22L, setOf(501L))
        doReturn(emptyList<space.nstart.pcp.pcp_satellites_service.entity.SatelliteModeBookedSlotEntity>())
            .`when`(bookedSlotRepository).findAllByMode_IdIn(org.mockito.ArgumentMatchers.anyList())
        doReturn(emptyList<space.nstart.pcp.pcp_satellites_service.entity.SatelliteModeCellEntity>())
            .`when`(cellRepository).findAllByMode_IdIn(org.mockito.ArgumentMatchers.anyList())

        val loaded = service.loadRevolutionConstraintModes(listOf(22L), intervalStart, intervalEnd)

        assertEquals(2, loaded.getValue(22L).size)
        assertTrue(loaded.getValue(22L).any { it.time == sameRevolutionOutsideDayWindow.startTime })
    }

    @Test
    fun `loadConstraintModes returns merged deduplicated context`() {
        val satelliteModeRepository = mock(SatelliteModeRepository::class.java)
        val satelliteModeSnapshotRepository = mock(SatelliteModeSnapshotRepository::class.java)
        val bookedSlotRepository = mock(SatelliteModeBookedSlotRepository::class.java)
        val cellRepository = mock(SatelliteModeCellRepository::class.java)
        val service = SatelliteModeQueryService(
            satelliteModeRepository = satelliteModeRepository,
            satelliteModeSnapshotRepository = satelliteModeSnapshotRepository,
            satelliteModeBookedSlotRepository = bookedSlotRepository,
            satelliteModeCellRepository = cellRepository,
            satelliteModeMapper = SatelliteModeMapper(),
            satelliteModeSnapshotMapper = SatelliteModeSnapshotMapper()
        )

        val intervalStart = LocalDateTime.of(2026, 3, 26, 10, 15)
        val intervalEnd = LocalDateTime.of(2026, 3, 26, 11, 45)
        val dayMode = modeEntity(
            id = 1L,
            revolution = 501L,
            startTime = LocalDateTime.of(2026, 3, 26, 12, 0),
            endTime = LocalDateTime.of(2026, 3, 26, 12, 10)
        )
        val sameRevolutionOutsideDayWindow = modeEntity(
            id = 2L,
            revolution = 501L,
            startTime = LocalDateTime.of(2026, 3, 25, 23, 40),
            endTime = LocalDateTime.of(2026, 3, 25, 23, 50)
        )

        doReturn(listOf(dayMode)).`when`(satelliteModeRepository)
            .findAllBySnapshot_SatelliteIdInAndSnapshot_IsActiveTrueAndEndTimeGreaterThanAndStartTimeLessThanOrderByStartTimeAsc(
                listOf(22L),
                LocalDateTime.of(2026, 3, 26, 0, 0),
                LocalDateTime.of(2026, 3, 27, 0, 0)
            )
        doReturn(listOf(dayMode, sameRevolutionOutsideDayWindow)).`when`(satelliteModeRepository)
            .findAllBySnapshot_SatelliteIdAndSnapshot_IsActiveTrueAndRevolutionInOrderByStartTimeAsc(22L, setOf(501L))
        doReturn(emptyList<space.nstart.pcp.pcp_satellites_service.entity.SatelliteModeBookedSlotEntity>())
            .`when`(bookedSlotRepository).findAllByMode_IdIn(org.mockito.ArgumentMatchers.anyList())
        doReturn(emptyList<space.nstart.pcp.pcp_satellites_service.entity.SatelliteModeCellEntity>())
            .`when`(cellRepository).findAllByMode_IdIn(org.mockito.ArgumentMatchers.anyList())

        val loaded = service.loadConstraintModes(listOf(22L), intervalStart, intervalEnd)

        assertEquals(2, loaded.getValue(22L).size)
        assertEquals(
            listOf(sameRevolutionOutsideDayWindow.startTime, dayMode.startTime),
            loaded.getValue(22L).map { it.time }
        )
    }

    private fun modeEntity(
        id: Long,
        revolution: Long,
        startTime: LocalDateTime,
        endTime: LocalDateTime
    ) = SatelliteModeEntity(
        id = id,
        satelliteId = 22L,
        source = SatelliteModeSource.COMPLAN,
        type = SatelliteModeType.SURVEY,
        startTime = startTime,
        endTime = endTime,
        revolution = revolution,
        duration = java.time.Duration.between(startTime, endTime).seconds.toDouble(),
        roll = 3.0
    )
}
