package space.nstart.pcp.slots_service.service

import com.sun.net.httpserver.HttpExchange
import com.sun.net.httpserver.HttpHandler
import com.sun.net.httpserver.HttpServer
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.ObjectProvider
import org.springframework.test.util.ReflectionTestUtils
import org.springframework.web.reactive.function.client.WebClient
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
import space.nstart.pcp.pcp_types_lib.dto.coverage.CoverageSchemeResponseDTO
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SurveySlotDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteObservationProfileDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteVisualizationDTO
import space.nstart.pcp.slots_service.configuration.CustomErrorException
import tools.jackson.databind.ObjectMapper
import java.net.InetSocketAddress
import java.time.LocalDateTime
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull

class CoverageSchemeServiceTest {

    private val objectMapper = ObjectMapper()
    private var server: HttpServer? = null

    @AfterEach
    fun tearDown() {
        server?.stop(0)
    }

    @Test
    fun `requestCoverageScheme maps coverageScheme slots to rows and czml`() {
        val timeStart = LocalDateTime.of(2026, 4, 14, 10, 0)
        val timeStop = LocalDateTime.of(2026, 4, 14, 12, 0)
        val serverUrl = startServer(
            mapOf(
                "/v1/requests/req-1/cells" to stringHandler(200, requestWithCellsJson()),
                "/api/coverage-schemes/calculate" to jsonHandler(
                    CoverageSchemeResponseDTO(
                        targetPolygonWkt = "POLYGON ((30 10, 40 40, 20 40, 30 10))",
                        coverageScheme = listOf(
                            SurveySlotDTO(
                                satelliteId = 11L,
                                tn = timeStart,
                                tk = timeStart.plusMinutes(5),
                                roll = 0.0,
                                contour = "POLYGON((30 10, 40 40, 20 40, 30 10))",
                                revolutionSign = RevolutionSign.ASC
                            ),
                            SurveySlotDTO(
                                satelliteId = 11L,
                                tn = timeStart,
                                tk = timeStart.plusMinutes(5),
                                roll = 0.0,
                                contour = "POLYGON((31 11, 32 12, 30 12, 31 11))",
                                revolutionSign = RevolutionSign.ASC
                            )
                        )
                    )
                ),
                "/api/satellites/11" to jsonHandler(
                    SatelliteDTO(
                        id = 11L,
                        code = "SAT-11",
                        name = "Sat-11",
                        typeCode = "TEST",
                        visualization = SatelliteVisualizationDTO(
                            red = 12,
                            green = 34,
                            blue = 56
                        ),
                        observationProfile = SatelliteObservationProfileDTO()
                    )
                )
            )
        )

        val service = createService(serverUrl)

        val response = service.requestCoverageScheme(
            requestId = "req-1",
            timeStart = timeStart,
            timeStop = timeStop,
            satelliteIds = listOf(11L, 22L),
            rollStepDegrees = 2.5,
            minimumTechnologyOverlap = 0.1,
            strictContinuousCoverage = false,
            allowPartialCoverage = true,
            orientationToleranceDegrees = 7.5,
            includeDebugInfo = false
        )

        assertEquals(2, response.slots.size)
        assertEquals(3, response.cov.size)
        assertEquals("document", response.cov.first().id)
        assertEquals(1, response.slots.first().sequence)
        assertEquals(2, response.slots.last().sequence)
        assertEquals(11L, response.slots.first().satelliteId)
        assertEquals(0.0, response.slots.first().roll)
        assertEquals(0.0, response.slots.last().roll)
        assertEquals(RevolutionSign.ASC, response.slots.first().revolutionSign)
        assertNotNull(response.cov[1].polygon)
        assertEquals(listOf(12, 34, 56, 255), response.cov[1].polygon?.outlineColor?.rgba?.toList())
    }

    @Test
    fun `requestCoverageScheme converts backend error to CustomErrorException`() {
        val serverUrl = startServer(
            mapOf(
                "/v1/requests/req-1/cells" to stringHandler(200, requestWithCellsJson()),
                "/api/coverage-schemes/calculate" to stringHandler(400, """{"message":"coverage failed"}""")
            )
        )

        val service = createService(serverUrl)

        val error = assertFailsWith<CustomErrorException> {
            service.requestCoverageScheme(
                requestId = "req-1",
                timeStart = LocalDateTime.of(2026, 4, 14, 10, 0),
                timeStop = LocalDateTime.of(2026, 4, 14, 12, 0),
                satelliteIds = listOf(11L),
                rollStepDegrees = null,
                minimumTechnologyOverlap = null,
                strictContinuousCoverage = null,
                allowPartialCoverage = null,
                orientationToleranceDegrees = null,
                includeDebugInfo = null
            )
        }

        assertEquals("coverage failed", error.message)
    }

    private fun createService(serverUrl: String): CoverageSchemeService {
        val provider = object : ObjectProvider<WebClient.Builder> {
            override fun getObject(vararg args: Any?): WebClient.Builder = WebClient.builder()
            override fun getObject(): WebClient.Builder = WebClient.builder()
            override fun getIfAvailable(): WebClient.Builder = WebClient.builder()
            override fun getIfUnique(): WebClient.Builder = WebClient.builder()
            override fun iterator(): MutableIterator<WebClient.Builder> = mutableListOf<WebClient.Builder>().iterator()
        }
        val service = CoverageSchemeService(provider)
        val earthService = EarthService(provider)
        val complexMissionService = ComplexMissionService(provider)

        ReflectionTestUtils.setField(service, "earthService", earthService)
        ReflectionTestUtils.setField(service, "complexMissionService", complexMissionService)
        ReflectionTestUtils.setField(service, "url", serverUrl)
        ReflectionTestUtils.setField(earthService, "url", serverUrl)
        ReflectionTestUtils.setField(complexMissionService, "complexMissionUrl", serverUrl)
        ReflectionTestUtils.setField(complexMissionService, "satelliteCatalogUrl", serverUrl)
        return service
    }

    private fun startServer(handlers: Map<String, HttpHandler>): String {
        val startedServer = HttpServer.create(InetSocketAddress(0), 0)
        handlers.forEach { (path, handler) ->
            startedServer.createContext(path, handler)
        }
        startedServer.start()
        server = startedServer
        return "http://localhost:${startedServer.address.port}"
    }

    private fun jsonHandler(body: Any) = HttpHandler { exchange ->
        respond(exchange, 200, objectMapper.writeValueAsString(body))
    }

    private fun stringHandler(status: Int, body: String) = HttpHandler { exchange ->
        respond(exchange, status, body)
    }

    private fun requestWithCellsJson(): String =
        """
        {
          "request": {
            "id": "11111111-1111-4111-8111-111111111111",
            "name": "coverage scheme request",
            "status": "ACCEPTED",
            "surveyType": "OPTICS",
            "geometry": "POLYGON ((30 10, 40 40, 20 40, 30 10))",
            "importance": 10.0,
            "beginDateTime": "2026-01-01T00:00:00Z",
            "endDateTime": "2026-01-02T00:00:00Z",
            "kpp": [],
            "highPriorityTransmit": false,
            "optics": null,
            "rsa": null,
            "coverage": {
              "requiredPercent": 100.0,
              "currentPercent": 0.0
            },
            "createdAt": "2026-01-01T00:00:00Z",
            "updatedAt": "2026-01-01T00:00:00Z",
            "deletedAt": null
          },
          "cells": []
        }
        """.trimIndent()

    private fun respond(exchange: HttpExchange, status: Int, body: String) {
        exchange.responseHeaders.add("Content-Type", "application/json")
        val bytes = body.toByteArray()
        exchange.sendResponseHeaders(status, bytes.size.toLong())
        exchange.responseBody.use { output -> output.write(bytes) }
    }
}
