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.dto.ComplexPlanRunResponseDTO
import space.nstart.pcp.pcp_satellites_service.dto.ComplexPlanRunStatisticsDTO
import space.nstart.pcp.pcp_satellites_service.dto.ComplexPlanRunSummaryDTO
import space.nstart.pcp.pcp_satellites_service.entity.ComplexPlanRunStatus
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 java.time.LocalDateTime
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull

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

    @LocalServerPort
    private var port: Int = 0

    @Autowired
    private lateinit var complexPlanRunPersistenceService: ComplexPlanRunPersistenceService

    @Autowired
    private lateinit var complexPlanRunRepository: ComplexPlanRunRepository

    @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 satelliteModePersistenceService: SatelliteModePersistenceService

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

    @Test
    fun `run details endpoint returns persisted run`() {
        val runId = createCompletedRun(listOf(22L), "api-user")

        val response = WebClient.create("http://localhost:$port")
            .get()
            .uri("/api/com-plan/runs/$runId")
            .retrieve()
            .bodyToMono(ComplexPlanRunResponseDTO::class.java)
            .block()!!

        assertEquals(runId, response.runId)
        assertEquals(ComplexPlanRunStatus.COMPLETED, response.status)
        assertEquals(listOf(22L), response.satelliteIds)
        assertEquals("api-user", response.requestedBy)
        assertEquals(3, response.calculatedModesCount)
        assertEquals(10.0, response.totalRequestsArea)
        assertEquals(4.0, response.coveredArea)
        assertEquals(40.0, response.coveredAreaPercent)
        assertNotNull(response.createdAt)
    }

    @Test
    fun `runs endpoint supports status and satellite filter`() {
        val completedRunId = createCompletedRun(listOf(22L), "one")
        createFailedRun(listOf(55L))

        val response = WebClient.create("http://localhost:$port")
            .get()
            .uri("/api/com-plan/runs?status=COMPLETED&satelliteId=22")
            .retrieve()
            .bodyToFlux(ComplexPlanRunSummaryDTO::class.java)
            .collectList()
            .block()
            .orEmpty()

        assertEquals(1, response.size)
        assertEquals(completedRunId, response.single().runId)
        assertEquals(ComplexPlanRunStatus.COMPLETED, response.single().status)
        assertEquals(listOf(22L), response.single().satelliteIds)
        assertEquals(40.0, response.single().coveredAreaPercent)
    }

    @Test
    fun `delete run endpoint removes persisted run`() {
        val runId = createCompletedRun(listOf(22L), "api-user")

        WebClient.create("http://localhost:$port")
            .delete()
            .uri("/api/com-plan/runs/$runId")
            .retrieve()
            .toBodilessEntity()
            .block()

        assertFalse(complexPlanRunRepository.existsById(runId))
    }

    @Test
    fun `run modes endpoint returns modes sorted by time`() {
        val runId = createCompletedRun(listOf(22L), "api-user")
        val run = complexPlanRunRepository.findById(runId).orElseThrow()
        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 = 2,
                        time = intervalStart.plusMinutes(20),
                        timeStop = intervalStart.plusMinutes(30),
                        roll = 5.0,
                        contourWKT = "POLYGON ((1 0, 2 0, 2 1, 1 1, 1 0))",
                        source = SatelliteModeSource.COMPLAN
                    ),
                    SurveyMode(
                        revolution = 1,
                        time = intervalStart.plusMinutes(5),
                        timeStop = intervalStart.plusMinutes(10),
                        roll = 4.0,
                        contourWKT = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
                        source = SatelliteModeSource.COMPLAN,
                        cellNums = listOf(42L)
                    )
                )
            ),
            complexPlanRun = run
        )

        val response = WebClient.create("http://localhost:$port")
            .get()
            .uri("/api/com-plan/runs/$runId/modes")
            .retrieve()
            .bodyToFlux(SatelliteModeResponseDTO::class.java)
            .collectList()
            .block()
            .orEmpty()

        assertEquals(listOf(1L, 2L), response.map { it.revolution })
        assertEquals(listOf(42L), response.first().cellNums)
    }

    private fun createCompletedRun(satelliteIds: List<Long>, requestedBy: String?): Long {
        val run = complexPlanRunPersistenceService.createRun(
            ComplexPlanRunCreateDTO(
                intervalStart = LocalDateTime.of(2026, 3, 26, 10, 0),
                intervalEnd = LocalDateTime.of(2026, 3, 26, 12, 0),
                satelliteIds = satelliteIds,
                requestedBy = requestedBy,
                requestPayload = "{}"
            )
        )
        complexPlanRunPersistenceService.markRunning(run.id!!)
        complexPlanRunPersistenceService.markCompleted(
            run.id!!,
            ComplexPlanRunStatisticsDTO(
                calculatedModesCount = 3,
                bookedModesCount = 1,
                complanModesCount = 1,
                mixedModesCount = 1,
                affectedSatellitesCount = satelliteIds.size,
                processedCellsCount = 2,
                bookedSlotLinksCount = 4,
                totalRequestsArea = 10.0,
                coveredArea = 4.0,
                coveredAreaPercent = 40.0
            )
        )
        return run.id!!
    }

    private fun createFailedRun(satelliteIds: List<Long>): Long {
        val run = complexPlanRunPersistenceService.createRun(
            ComplexPlanRunCreateDTO(
                intervalStart = LocalDateTime.of(2026, 3, 26, 15, 0),
                intervalEnd = LocalDateTime.of(2026, 3, 26, 16, 0),
                satelliteIds = satelliteIds,
                requestedBy = null,
                requestPayload = "{}"
            )
        )
        complexPlanRunPersistenceService.markRunning(run.id!!)
        complexPlanRunPersistenceService.markFailed(run.id!!, "boom")
        return run.id!!
    }
}
