package space.nstart.pcp.slots_service.model.satellite

import org.junit.jupiter.api.Test
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.BookedSlotStatus
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotStatus
import space.nstart.pcp.slots_service.entity.BookedRequestEntity
import space.nstart.pcp.slots_service.entity.BookedSlotEntity
import space.nstart.pcp.slots_service.entity.SlotEntity
import java.time.LocalDateTime
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue

class TestSatelliteImplBookedBlockingTest {

    @Test
    fun `non-booked slot inside active booked interval is marked busy and exact booked slot stays booked`() {
        val baseTime = LocalDateTime.of(2026, 5, 21, 0, 0)
        val satellite = TestSatelliteImpl(
            id = 1L,
            tnCalc = baseTime,
            durationCalc = 1,
            maxChainLength = 3,
            maxSurveyDuration = 300,
            mmi = 10
        )
        val bookedRawSlot = slot(slotId = 1L, slotNum = 1L, baseTime = baseTime, startSecond = 0, endSecond = 10)
        val candidateInsideBooked = slot(slotId = 2L, slotNum = 2L, baseTime = baseTime, startSecond = 2, endSecond = 8)
        val booked = BookedSlotEntity(slot = bookedRawSlot, cycle = 0).apply {
            status = BookedSlotStatus.PROCESSED.name
        }

        val result = satellite.prepareCoverageSlots(
            slots = listOf(bookedRawSlot, candidateInsideBooked),
            bookedSlots = listOf(booked),
            timeStart = baseTime,
            timeStop = baseTime.plusSeconds(20),
            sign = null,
            states = null
        ).map { it.slot }.associateBy { it.slotNumber }

        assertEquals(SlotStatus.BOOKED, result.getValue(1L).state)
        assertEquals(SlotStatus.BUSY, result.getValue(2L).state)
    }

    @Test
    fun `busy slot carries holders from blocking booking, booked and available slots carry none`() {
        val baseTime = LocalDateTime.of(2026, 5, 21, 0, 0)
        val satellite = TestSatelliteImpl(
            id = 1L,
            tnCalc = baseTime,
            durationCalc = 1,
            maxChainLength = 3,
            maxSurveyDuration = 300,
            mmi = 10
        )
        val bookedRawSlot = slot(slotId = 1L, slotNum = 1L, baseTime = baseTime, startSecond = 0, endSecond = 10)
        val candidateInsideBooked = slot(slotId = 2L, slotNum = 2L, baseTime = baseTime, startSecond = 2, endSecond = 8)
        // Свободный кандидат вне интервала и mmi-окна брони — держателей быть не должно.
        val freeSlot = slot(slotId = 3L, slotNum = 3L, baseTime = baseTime, startSecond = 100, endSecond = 110)
        // Множественный держатель: одну бронь держат две заявки.
        val bookedWithRequests = BookedSlotEntity(
            slot = bookedRawSlot,
            cycle = 0,
            requests = mutableListOf(
                BookedRequestEntity(requestId = "req-A"),
                BookedRequestEntity(requestId = "req-B"),
            ),
            status = BookedSlotStatus.PROCESSED.name,
        )

        val result = satellite.prepareCoverageSlots(
            slots = listOf(bookedRawSlot, candidateInsideBooked, freeSlot),
            bookedSlots = listOf(bookedWithRequests),
            timeStart = baseTime,
            timeStop = baseTime.plusSeconds(120),
            sign = null,
            states = null
        ).map { it.slot }.associateBy { it.slotNumber }

        val busy = result.getValue(2L)
        assertEquals(SlotStatus.BUSY, busy.state)
        assertEquals(listOf("req-A", "req-B"), busy.holders.orEmpty().map { it.requestId }.sorted())
        // На уровне прогноза держателям проставлен только requestId — name/importance добавит reqCover.
        assertTrue(busy.holders.orEmpty().all { it.name == null && it.importance == null })
        assertNull(result.getValue(1L).holders, "у самой брони (BOOKED) держателей не показываем")
        assertNull(result.getValue(3L).holders, "у свободного слота держателей нет")
    }

    private fun slot(
        slotId: Long,
        slotNum: Long,
        baseTime: LocalDateTime,
        startSecond: Long,
        endSecond: Long
    ) = SlotEntity(
        slotId = slotId,
        slotNum = slotNum,
        cycle = 0,
        satelliteId = 1L,
        coveringType = 0,
        tn = baseTime.plusSeconds(startSecond),
        tk = baseTime.plusSeconds(endSecond),
        roll = 10.0,
        contour = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
        revolution = 1L,
        revolutionSign = "ASC"
    )
}
