package space.nstart.pcp.slots_service.service

import com.sun.net.httpserver.HttpExchange
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.slots_service.configuration.WebClientConfig
import java.net.InetSocketAddress
import java.net.URI
import java.time.LocalDateTime
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.test.assertEquals

class ComplexMissionServiceTest {

    private var server: HttpServer? = null

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

    @Test
    fun `complex plan run methods call complex mission endpoints`() {
        val requests = CopyOnWriteArrayList<URI>()
        val methods = CopyOnWriteArrayList<String>()
        val serverUrl = startServer(requests, methods)
        val service = createService(serverUrl)

        val runs = service.complexPlanRuns()
        val run = service.complexPlanRun(17L)
        val modes = service.complexPlanRunModes(17L)
        service.deleteComplexPlanRun(17L)

        assertEquals(1, runs.size())
        assertEquals(17, runs[0]["runId"].asInt())
        assertEquals(17, run["runId"].asInt())
        assertEquals(0, modes.size())
        assertEquals(
            listOf(
                "/api/com-plan/runs",
                "/api/com-plan/runs/17",
                "/api/com-plan/runs/17/modes",
                "/api/com-plan/runs/17"
            ),
            requests.map { it.path }
        )
        assertEquals(listOf("GET", "GET", "GET", "DELETE"), methods)
    }

    @Test
    fun `complex plan run modes supports report payloads larger than previous webclient buffer`() {
        val itemPayload = "x".repeat(1000)
        val itemCount = 22_000
        val modesBody = buildString {
            append("[")
            repeat(itemCount) { index ->
                if (index > 0) {
                    append(",")
                }
                append("""{"slotNumber":$index,"payload":"$itemPayload"}""")
            }
            append("]")
        }
        val serverUrl = startServer(
            requests = CopyOnWriteArrayList(),
            modesBody = modesBody
        )
        val service = createService(serverUrl) {
            WebClientConfig(32 * 1024 * 1024).webClientBuilder()
        }

        val modes = service.complexPlanRunModes(17L)

        assertEquals(itemCount, modes.size())
        assertEquals(itemPayload.length, modes[0]["payload"].asText().length)
    }

    @Test
    fun `plan calls satellite scoped modes endpoint with runId`() {
        val requests = CopyOnWriteArrayList<URI>()
        val serverUrl = startServer(requests)
        val service = createService(serverUrl)
        val intervalStart = LocalDateTime.of(2026, 4, 22, 10, 0)
        val intervalEnd = LocalDateTime.of(2026, 4, 22, 12, 0)

        val response = service.plan(101L, intervalStart, intervalEnd, 17L)
            .collectList()
            .block()
            .orEmpty()

        assertEquals(emptyList(), response)
        assertEquals("/api/satellites/101/modes", requests.single().path)
        assertEquals(
            "intervalStart=2026-04-22T10:00&intervalEnd=2026-04-22T12:00&runId=17",
            requests.single().rawQuery
        )
    }

    private fun createService(
        serverUrl: String,
        builderFactory: () -> WebClient.Builder = { WebClient.builder() }
    ): ComplexMissionService {
        val provider = object : ObjectProvider<WebClient.Builder> {
            override fun getObject(vararg args: Any?): WebClient.Builder = builderFactory()
            override fun getObject(): WebClient.Builder = builderFactory()
            override fun getIfAvailable(): WebClient.Builder = builderFactory()
            override fun getIfUnique(): WebClient.Builder = builderFactory()
            override fun iterator(): MutableIterator<WebClient.Builder> = mutableListOf<WebClient.Builder>().iterator()
        }
        val service = ComplexMissionService(provider)
        ReflectionTestUtils.setField(service, "complexMissionUrl", serverUrl)
        ReflectionTestUtils.setField(service, "satelliteCatalogUrl", serverUrl)
        return service
    }

    private fun startServer(
        requests: MutableList<URI>,
        methods: MutableList<String> = mutableListOf(),
        modesBody: String = "[]"
    ): String {
        val startedServer = HttpServer.create(InetSocketAddress(0), 0)
        startedServer.createContext("/api/com-plan/runs") { exchange ->
            requests.add(exchange.requestURI)
            methods.add(exchange.requestMethod)
            if (exchange.requestMethod == "DELETE") {
                respondNoContent(exchange)
            } else {
                respond(
                    exchange,
                    if (exchange.requestURI.path.endsWith("/modes")) {
                        modesBody
                    } else if (exchange.requestURI.path.endsWith("/17")) {
                        """{"runId":17,"status":"COMPLETED"}"""
                    } else {
                        """[{"runId":17,"status":"COMPLETED"}]"""
                    }
                )
            }
        }
        startedServer.createContext("/api/satellites/101/modes") { exchange ->
            requests.add(exchange.requestURI)
            methods.add(exchange.requestMethod)
            respond(exchange, "[]")
        }
        startedServer.start()
        server = startedServer
        return "http://localhost:${startedServer.address.port}"
    }

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

    private fun respondNoContent(exchange: HttpExchange) {
        exchange.sendResponseHeaders(204, -1)
        exchange.close()
    }
}
