package space.nstart.pcp.slots_service.controller

import org.junit.jupiter.api.Test
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.doThrow
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.springframework.test.util.ReflectionTestUtils
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotCalculationSummaryDTO
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotCoverageStrategy
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotDTO
import space.nstart.pcp.slots_service.configuration.CustomValidationException
import space.nstart.pcp.slots_service.configuration.GlobalExceptionHandler
import space.nstart.pcp.slots_service.service.SlotService
import java.time.LocalDateTime

class SlotControllerTest {

    private val slotService = mock(SlotService::class.java)
    private val controller = SlotController().also {
        ReflectionTestUtils.setField(it, "slotService", slotService)
    }
    private val mockMvc: MockMvc = MockMvcBuilders.standaloneSetup(controller)
        .setControllerAdvice(GlobalExceptionHandler())
        .build()

    @Test
    fun `interval endpoint delegates to slot service`() {
        val satelliteId = 56756L
        val timeStart = LocalDateTime.of(2026, 4, 20, 0, 0)
        val timeStop = LocalDateTime.of(2026, 4, 21, 0, 0)
        doReturn(
            listOf(
                SlotDTO(
                    cycle = 2,
                    satelliteId = 56756L,
                    tn = LocalDateTime.of(2026, 4, 20, 1, 0),
                    tk = LocalDateTime.of(2026, 4, 20, 1, 15),
                    roll = 12.5,
                    contour = "POLYGON ((0 0, 0 1, 1 1, 0 0))",
                    revolution = 44L,
                    revolutionSign = RevolutionSign.ASC,
                    slotNumber = 101L
                )
            )
        ).`when`(slotService).allByInterval(satelliteId, timeStart, timeStop)

        mockMvc.perform(
            get("/api/slots/interval")
                .param("satelliteId", satelliteId.toString())
                .param("timeStart", timeStart.toString())
                .param("timeStop", timeStop.toString())
        )
            .andExpect(status().isOk)
            .andExpect(jsonPath("$[0].satelliteId").value(56756L))
            .andExpect(jsonPath("$[0].slotNumber").value(101L))

        verify(slotService).allByInterval(satelliteId, timeStart, timeStop)
    }

    @Test
    fun `poly cover endpoint delegates coverage strategy to slot service`() {
        val wkt = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"
        val timeStart = LocalDateTime.of(2026, 4, 20, 0, 0)
        val timeStop = LocalDateTime.of(2026, 4, 21, 0, 0)
        doReturn(emptyList<SlotDTO>()).`when`(slotService).polySlots(
            wkt,
            timeStart,
            timeStop,
            RevolutionSign.ASC,
            true,
            null,
            listOf(22L),
            12.5,
            SlotCoverageStrategy.CONTINUOUS
        )

        mockMvc.perform(
            get("/api/slots/poly-cover")
                .param("wkt", wkt)
                .param("timeStart", timeStart.toString())
                .param("timeStop", timeStop.toString())
                .param("revSign", "ASC")
                .param("cov", "true")
                .param("satellites", "22")
                .param("sun", "12.5")
                .param("coverageStrategy", "CONTINUOUS")
        )
            .andExpect(status().isOk)

        verify(slotService).polySlots(
            wkt,
            timeStart,
            timeStop,
            RevolutionSign.ASC,
            true,
            null,
            listOf(22L),
            12.5,
            SlotCoverageStrategy.CONTINUOUS
        )
    }

    @Test
    fun `request cover endpoint delegates coverage strategy to slot service`() {
        val timeStart = LocalDateTime.of(2026, 4, 20, 0, 0)
        val timeStop = LocalDateTime.of(2026, 4, 21, 0, 0)
        doReturn(emptyList<SlotDTO>()).`when`(slotService).reqCover(
            "req-1",
            timeStart,
            timeStop,
            RevolutionSign.ASC,
            true,
            listOf(22L),
            12.5,
            SlotCoverageStrategy.CONTINUOUS
        )

        mockMvc.perform(
            get("/api/slots/request-cover")
                .param("requestId", "req-1")
                .param("timeStart", timeStart.toString())
                .param("timeStop", timeStop.toString())
                .param("revSign", "ASC")
                .param("cov", "true")
                .param("satellites", "22")
                .param("sun", "12.5")
                .param("coverageStrategy", "CONTINUOUS")
        )
            .andExpect(status().isOk)

        verify(slotService).reqCover(
            "req-1",
            timeStart,
            timeStop,
            RevolutionSign.ASC,
            true,
            listOf(22L),
            12.5,
            SlotCoverageStrategy.CONTINUOUS
        )
    }

    @Test
    fun `interval endpoint returns bad request on invalid interval`() {
        val satelliteId = 56756L
        val timeStart = LocalDateTime.of(2026, 4, 21, 0, 0)
        val timeStop = LocalDateTime.of(2026, 4, 20, 0, 0)
        doThrow(CustomValidationException("Параметр timeStop должен быть больше или равен timeStart"))
            .`when`(slotService).allByInterval(satelliteId, timeStart, timeStop)

        mockMvc.perform(
            get("/api/slots/interval")
                .param("satelliteId", satelliteId.toString())
                .param("timeStart", timeStart.toString())
                .param("timeStop", timeStop.toString())
        )
            .andExpect(status().isBadRequest)
            .andExpect(jsonPath("$.error").value("Параметр timeStop должен быть больше или равен timeStart"))
    }

    @Test
    fun `calculation summary endpoint delegates to slot service`() {
        doReturn(
            listOf(
                SlotCalculationSummaryDTO.calculated(
                    satelliteId = 22L,
                    slotCount = 12L,
                    minTime = LocalDateTime.of(2026, 4, 20, 0, 0),
                    maxTime = LocalDateTime.of(2026, 4, 21, 0, 0),
                    minRevolution = 100L,
                    maxRevolution = 112L
                )
            )
        ).`when`(slotService).slotCalculationSummaries(listOf(22L, 44L))

        mockMvc.perform(
            get("/api/slots/calculation-summary")
                .param("satelliteIds", "22")
                .param("satelliteIds", "44")
        )
            .andExpect(status().isOk)
            .andExpect(jsonPath("$[0].satelliteId").value(22L))
            .andExpect(jsonPath("$[0].calculated").value(true))
            .andExpect(jsonPath("$[0].slotCount").value(12L))
            .andExpect(jsonPath("$[0].revolutionInterval").value(12L))

        verify(slotService).slotCalculationSummaries(listOf(22L, 44L))
    }
}
