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.junit.jupiter.api.assertThrows
import org.springframework.beans.factory.ObjectProvider
import org.springframework.http.HttpStatus
import org.springframework.test.util.ReflectionTestUtils
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.server.ResponseStatusException
import space.nstart.pcp.slots_service.dto.tgu.TguPlanDecision
import java.net.InetSocketAddress
import java.net.URI
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.test.assertEquals

class TguPlanningServiceTest {

    private var server: HttpServer? = null

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

    @Test
    fun `read methods call existing tgu endpoints`() {
        val requests = CopyOnWriteArrayList<URI>()
        val service = createService(startServer(requests))

        val platforms = service.platforms()
        val plans = service.plans(7)
        val spacecraftPlans = service.spacecraftPlans("56756", 14)

        assertEquals("Kondor-FKA No.2", platforms.single().name)
        assertEquals("56756", plans.single().spacecraftId)
        assertEquals("WAITING_DECISION", spacecraftPlans.single().status)
        assertEquals(
            listOf("/api/platforms", "/api/plans", "/api/plans/56756"),
            requests.map { it.path }
        )
        assertEquals(listOf(null, "historyDays=7", "historyDays=14"), requests.map { it.rawQuery })
    }

    @Test
    fun `decision method calls test controller endpoint`() {
        val requests = CopyOnWriteArrayList<URI>()
        val service = createService(startServer(requests))

        val response = service.sendDecision(
            java.util.UUID.fromString("6d0941e4-61d6-4f96-8fa8-90ba11db121e"),
            TguPlanDecision.ACCEPTED,
            "UI TEST ACCEPTED"
        )

        assertEquals(TguPlanDecision.ACCEPTED, response.decision)
        assertEquals("/test/tgu/plans/6d0941e4-61d6-4f96-8fa8-90ba11db121e/decision", requests.last().path)
        assertEquals("decision=ACCEPTED&reason=UI%20TEST%20ACCEPTED", requests.last().rawQuery)
    }

    @Test
    fun `decision conflict is mapped to operator message`() {
        val service = createService(
            startServer(
                statusByPath = mapOf(
                    "/test/tgu/plans/00000000-0000-0000-0000-000000000000/decision" to 409
                )
            )
        )

        val exception = assertThrows<ResponseStatusException> {
            service.sendDecision(
                java.util.UUID.fromString("00000000-0000-0000-0000-000000000000"),
                TguPlanDecision.REJECTED,
                "reason"
            )
        }

        assertEquals(HttpStatus.CONFLICT, exception.statusCode)
        assertEquals(
            "План не является активным планом, ожидающим решения, или для него нет active attempt.",
            exception.reason
        )
    }

    private fun createService(serverUrl: String): TguPlanningService {
        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 = TguPlanningService(provider)
        ReflectionTestUtils.setField(service, "tguServiceUrl", serverUrl)
        return service
    }

    private fun startServer(
        requests: MutableList<URI> = CopyOnWriteArrayList(),
        statusByPath: Map<String, Int> = emptyMap()
    ): String {
        val startedServer = HttpServer.create(InetSocketAddress(0), 0)
        startedServer.createContext("/") { exchange ->
            requests.add(exchange.requestURI)
            val forcedStatus = statusByPath[exchange.requestURI.path]
            when {
                forcedStatus != null -> respond(exchange, forcedStatus, """{"error":"conflict"}""")
                exchange.requestURI.path == "/api/platforms" -> respond(
                    exchange,
                    200,
                    """[{"id":"nsi-id","businessKey":"2024-149E","name":"Kondor-FKA No.2","status":"OPERATIONAL","noradId":56756,"mission":"KONDOR_FKA","validFrom":"2026-05-21T15:06:00Z","validTo":"9999-12-31T23:59:59Z"}]"""
                )
                exchange.requestURI.path == "/api/plans" || exchange.requestURI.path == "/api/plans/56756" -> respond(
                    exchange,
                    200,
                    """[{"planId":"6d0941e4-61d6-4f96-8fa8-90ba11db121e","spacecraftId":"56756","startTime":"2026-05-29T10:00:00Z","endTime":"2026-05-29T10:15:00Z","kppId":"KPP-1","status":"WAITING_DECISION","basePlanId":null,"projected":false,"dismissedAt":null}]"""
                )
                exchange.requestURI.path.startsWith("/test/tgu/plans/") -> respond(
                    exchange,
                    200,
                    """{"eventId":"1c857555-f02b-4051-b56a-f112b842624a","spacecraftId":"56756","planId":"6d0941e4-61d6-4f96-8fa8-90ba11db121e","attemptId":"a82394f2-545f-4cd6-9ab1-876ca403d33a","decision":"ACCEPTED","decisionTime":"2026-05-29T10:01:00","reason":"UI TEST ACCEPTED"}"""
                )
                else -> respond(exchange, 404, """{"error":"not found"}""")
            }
        }
        startedServer.start()
        server = startedServer
        return "http://localhost:${startedServer.address.port}"
    }

    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) }
    }
}
