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.pcp_types_lib.dto.ballistics.InitialConditionsDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.MovementModel
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
import java.net.InetSocketAddress
import java.net.URI
import java.time.LocalDateTime
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

class BallisticsServiceTest {

    private var server: HttpServer? = null

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

    @Test
    fun `sat data methods forward time interval to ballistics service`() {
        val requests = CopyOnWriteArrayList<URI>()
        val requestBodies = CopyOnWriteArrayList<String>()
        val serverUrl = startServer(requests, requestBodies)
        val service = createService(serverUrl)
        val timeStart = LocalDateTime.of(2026, 4, 21, 10, 0)
        val timeStop = LocalDateTime.of(2026, 4, 21, 12, 0)

        service.ascNodes(101L, timeStart, timeStop).collectList().block()
        service.flightLine(101L, timeStart, timeStop).collectList().block()
        service.points(101L, timeStart, timeStop).collectList().block()
        val availability = service.orbitAvailability()
        val exactPoint = service.exactTimePoint(101L, timeStart)
        service.receiveRv(
            SatelliteICDTO(
                satelliteId = 101L,
                movementModel = MovementModel.KONDOR,
                ic = InitialConditionsDTO(
                    orbPoint = OrbPointDTO(
                        time = timeStart,
                        revolution = 7L,
                        vx = 1.0,
                        vy = 2.0,
                        vz = 3.0,
                        x = 4.0,
                        y = 5.0,
                        z = 6.0
                    )
                )
            )
        )
        val currentInitialConditions = service.currentInitialConditions(101L)
        val tleRevolutions = service.tleRevolutions(
            TLEDTO(
                first = "1 25994U 99068A   26167.13049375  .00000315  00000-0  73126-4 0  9997",
                second = "2 25994  97.9457 216.7186 0001501 193.3628 244.8059 14.61102329409455"
            ),
            days = 1.5
        )

        assertEquals(
            listOf(
                "/api/satellites/101/asc-node",
                "/api/satellites/101/flight-line",
                "/api/satellites/101/orbit",
                "/api/satellites/orbit/availability",
                "/api/satellites/101/extract-time",
                "/api/satellites/receive-rv",
                "/api/satellites/101/initial-conditions/current",
                "/api/tle/revolutions"
            ),
            requests.map { it.path }
        )
        assertEquals(
            listOf(
                "time_start=2026-04-21T10:00&time_stop=2026-04-21T12:00",
                "time_start=2026-04-21T10:00&time_stop=2026-04-21T12:00",
                "time_start=2026-04-21T10:00&time_stop=2026-04-21T12:00",
                null,
                null,
                null,
                null,
                null
            ),
            requests.map { it.query }
        )
        assertEquals(1, availability.size)
        assertEquals(101L, availability.single().satelliteId)
        assertNotNull(exactPoint)
        assertEquals(timeStart, exactPoint.time)
        assertNotNull(currentInitialConditions)
        assertEquals(101L, currentInitialConditions.satelliteId)
        assertEquals(emptyList(), tleRevolutions)
        assertEquals(2, requestBodies.size)
        assertEquals(true, requestBodies.any { it.contains(""""movementModel":"KONDOR"""") })
        assertEquals(true, requestBodies.any { it.contains(""""days":1.5""") })
    }

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

    private fun startServer(requests: MutableList<URI>, requestBodies: MutableList<String>): String {
        val startedServer = HttpServer.create(InetSocketAddress(0), 0)
        listOf(
            "/api/satellites/101/asc-node",
            "/api/satellites/101/flight-line",
            "/api/satellites/101/orbit"
        ).forEach { path ->
            startedServer.createContext(path) { exchange ->
                requests.add(exchange.requestURI)
                respond(exchange, "[]")
            }
        }
        startedServer.createContext("/api/satellites/orbit/availability") { exchange ->
            requests.add(exchange.requestURI)
            respond(
                exchange,
                """[{"satelliteId":101,"timeStart":"2026-04-21T10:00:00","timeStop":"2026-04-21T12:00:00"}]"""
            )
        }
        startedServer.createContext("/api/satellites/101/extract-time") { exchange ->
            requests.add(exchange.requestURI)
            respond(
                exchange,
                """[{"time":"2026-04-21T10:00:00","revolution":1,"vx":1.0,"vy":2.0,"vz":3.0,"x":4.0,"y":5.0,"z":6.0}]"""
            )
        }
        startedServer.createContext("/api/satellites/receive-rv") { exchange ->
            requests.add(exchange.requestURI)
            requestBodies.add(exchange.requestBody.bufferedReader().use { it.readText() })
            respond(exchange, "")
        }
        startedServer.createContext("/api/satellites/101/initial-conditions/current") { exchange ->
            requests.add(exchange.requestURI)
            respond(
                exchange,
                """
                {
                  "satelliteId": 101,
                  "movementModel": "KONDOR",
                  "ic": {
                    "orbPoint": {
                      "time": "2026-04-21T10:00:00",
                      "revolution": 7,
                      "vx": 1.0,
                      "vy": 2.0,
                      "vz": 3.0,
                      "x": 4.0,
                      "y": 5.0,
                      "z": 6.0
                    },
                    "sBall": 0.0,
                    "f81": 147.8
                  }
                }
                """.trimIndent()
            )
        }
        startedServer.createContext("/api/tle/revolutions") { exchange ->
            requests.add(exchange.requestURI)
            requestBodies.add(exchange.requestBody.bufferedReader().use { it.readText() })
            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) }
    }
}
