package space.nstart.pcp.pcp_satellites_service.service

import com.sun.net.httpserver.HttpServer
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
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.requests.slots.SlotCoverageBatchResponseDTO
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotCoverageStrategy
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotCoverageTargetDTO
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotDTO
import tools.jackson.databind.ObjectMapper
import java.net.InetSocketAddress
import java.time.LocalDateTime
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class CoverageBatchClientTest {

    private var server: HttpServer? = null

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

    @Test
    fun `coverageModes sends sun and coverage strategy in batch request body`() {
        var requestBody = ""
        server = HttpServer.create(InetSocketAddress(0), 0).apply {
            createContext("/api/slots/poly-cover/batch") { exchange ->
                requestBody = exchange.requestBody.bufferedReader().readText()
                val responseBody = "[]"
                exchange.responseHeaders.add("Content-Type", "application/json")
                exchange.sendResponseHeaders(200, responseBody.toByteArray().size.toLong())
                exchange.responseBody.use { it.write(responseBody.toByteArray()) }
            }
            start()
        }

        val builderProvider = mock(ObjectProvider::class.java) as ObjectProvider<WebClient.Builder>
        `when`(builderProvider.ifAvailable).thenReturn(WebClient.builder())

        val client = CoverageBatchClient(builderProvider)
        ReflectionTestUtils.setField(client, "url", "http://localhost:${server!!.address.port}")

        client.coverageModes(
            targets = listOf(SlotCoverageTargetDTO(targetId = 101L, wkt = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))")),
            intervalStart = LocalDateTime.of(2026, 3, 26, 10, 0),
            intervalEnd = LocalDateTime.of(2026, 3, 26, 12, 0),
            satelliteIds = listOf(22L),
            occupiedIntervals = emptyList(),
            sun = 12.5,
            coverageStrategy = SlotCoverageStrategy.CONTINUOUS
        )

        assertTrue(requestBody.contains("\"sun\":12.5"))
        assertTrue(requestBody.contains("\"coverageStrategy\":\"CONTINUOUS\""))
    }

    @Test
    fun `coverageModes falls back to GREEDY when CONTINUOUS batch request returns server error`() {
        val requestBodies = mutableListOf<String>()
        val requestCounter = AtomicInteger()
        server = HttpServer.create(InetSocketAddress(0), 0).apply {
            createContext("/api/slots/poly-cover/batch") { exchange ->
                val requestBody = exchange.requestBody.bufferedReader().readText()
                requestBodies += requestBody
                val attempt = requestCounter.incrementAndGet()
                if (attempt == 1) {
                    val responseBody = """{"error":"solver failed"}"""
                    exchange.responseHeaders.add("Content-Type", "application/json")
                    exchange.sendResponseHeaders(500, responseBody.toByteArray().size.toLong())
                    exchange.responseBody.use { it.write(responseBody.toByteArray()) }
                } else {
                    val responseBody = ObjectMapper().writeValueAsString(
                        listOf(
                            SlotCoverageBatchResponseDTO(
                                targetId = 101L,
                                slots = listOf(
                                    SlotDTO(
                                        cycle = 0,
                                        satelliteId = 22,
                                        tn = LocalDateTime.of(2026, 3, 26, 10, 0),
                                        tk = LocalDateTime.of(2026, 3, 26, 10, 10),
                                        roll = 10.0,
                                        contour = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
                                        revolution = 501,
                                        latitude = 10.0,
                                        longitude = 20.0
                                    )
                                )
                            )
                        )
                    )
                    exchange.responseHeaders.add("Content-Type", "application/json")
                    exchange.sendResponseHeaders(200, responseBody.toByteArray().size.toLong())
                    exchange.responseBody.use { it.write(responseBody.toByteArray()) }
                }
            }
            start()
        }

        val builderProvider = mock(ObjectProvider::class.java) as ObjectProvider<WebClient.Builder>
        `when`(builderProvider.ifAvailable).thenReturn(WebClient.builder())

        val client = CoverageBatchClient(builderProvider)
        ReflectionTestUtils.setField(client, "url", "http://localhost:${server!!.address.port}")

        val response = client.coverageModes(
            targets = listOf(SlotCoverageTargetDTO(targetId = 101L, wkt = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))")),
            intervalStart = LocalDateTime.of(2026, 3, 26, 10, 0),
            intervalEnd = LocalDateTime.of(2026, 3, 26, 12, 0),
            satelliteIds = listOf(22L),
            occupiedIntervals = emptyList(),
            sun = null,
            coverageStrategy = SlotCoverageStrategy.CONTINUOUS
        )

        assertEquals(2, requestBodies.size)
        assertTrue(requestBodies[0].contains("\"coverageStrategy\":\"CONTINUOUS\""))
        assertTrue(requestBodies[1].contains("\"coverageStrategy\":\"GREEDY\""))
        assertEquals(1, response.size)
        assertEquals(1, response.getValue(101L).size)
    }
}
