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.util.unit.DataSize
import org.springframework.web.reactive.function.client.WebClient
import space.nstart.pcp.pcp_satellites_service.configuration.WebClientConfig
import java.net.InetSocketAddress
import java.net.URI
import kotlin.test.assertEquals
import kotlin.test.assertFalse

class EarthGridServiceTest {

    private var server: HttpServer? = null

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

    @Test
    fun `cells calls v1 cells with requests and maps wrapper items`() {
        val requestedUris = mutableListOf<URI>()
        server = serverWithCellsResponse(requestedUris)

        val cells = earthGridService().cells().toList()
        val cell = cells.single()
        val request = cell.requests.single()

        assertEquals("/v1/cells/priority-map", requestedUris.single().path)
        assertEquals("minImportance=0.001", requestedUris.single().query)
        assertFalse(requestedUris.any { uri -> uri.path == "/api/earth-grid/with-requests" })
        assertEquals(1L, cell.id)
        assertEquals(1L, cell.num)
        assertEquals(55.5, cell.latitude)
        assertEquals(37.5, cell.longitude)
        assertEquals(10.0, cell.importance)
        assertEquals("POLYGON ((30 50, 31 50, 31 51, 30 51, 30 50))", cell.contour)
        assertEquals("00000000-0000-0000-0000-000000000001", request.requestId)
        assertEquals(42.5, request.covPercent)
        assertEquals(9.0, request.importance)
        assertEquals("POLYGON ((30.1 50.1, 30.2 50.1, 30.2 50.2, 30.1 50.2, 30.1 50.1))", request.contour)
    }

    @Test
    fun `cells forwards countLat and countLong to v1 cells with requests`() {
        val requestedUris = mutableListOf<URI>()
        server = serverWithCellsResponse(requestedUris)

        earthGridService().cells(countLat = 12, countLong = 24).toList()

        assertEquals("/v1/cells/priority-map", requestedUris.single().path)
        assertEquals("minImportance=0.001&countLat=12&countLong=24", requestedUris.single().query)
        assertFalse(requestedUris.any { uri -> uri.path == "/api/earth-grid/with-requests" })
    }

    @Test
    fun `cells supports response larger than default webclient buffer`() {
        val itemCount = 4_000
        val contour = "POLYGON ((${(1..80).joinToString(",") { index -> "$index $index" }}))"
        val responseBody = buildString {
            append("""{"items":[""")
            repeat(itemCount) { index ->
                if (index > 0) {
                    append(",")
                }
                append(
                    """
                    {
                      "cellNum": $index,
                      "latitude": 55.5,
                      "longitude": 37.5,
                      "importance": 10.0,
                      "contour": "$contour",
                      "requests": []
                    }
                    """.trimIndent()
                )
            }
            append("]}")
        }
        val requestedUris = mutableListOf<URI>()
        server = serverWithCellsResponse(requestedUris, responseBody)

        val cells = earthGridService {
            WebClientConfig(DataSize.ofMegabytes(20)).webClientBuilder()
        }.cells().toList()

        assertEquals(itemCount, cells.size)
        assertEquals("/v1/cells/priority-map", requestedUris.single().path)
        assertEquals("minImportance=0.001", requestedUris.single().query)
    }

    private fun serverWithCellsResponse(requestedUris: MutableList<URI>): HttpServer =
        serverWithCellsResponse(
            requestedUris,
            """
                {
                  "items": [
                    {
                      "cellNum": 1,
                      "latitude": 55.5,
                      "longitude": 37.5,
                      "importance": 10.0,
                      "contour": "POLYGON ((30 50, 31 50, 31 51, 30 51, 30 50))",
                      "requests": [
                        {
                          "requestId": "00000000-0000-0000-0000-000000000001",
                          "contour": "POLYGON ((30.1 50.1, 30.2 50.1, 30.2 50.2, 30.1 50.2, 30.1 50.1))",
                          "coveragePercent": 42.5,
                          "importance": 9.0
                        }
                      ]
                    }
                  ]
                }
            """.trimIndent()
        )

    private fun serverWithCellsResponse(
        requestedUris: MutableList<URI>,
        responseBody: String
    ): HttpServer =
        HttpServer.create(InetSocketAddress(0), 0).apply {
            createContext("/v1/cells/priority-map") { exchange ->
                requestedUris += exchange.requestURI
                exchange.responseHeaders.add("Content-Type", "application/json")
                exchange.sendResponseHeaders(200, responseBody.toByteArray().size.toLong())
                exchange.responseBody.use { it.write(responseBody.toByteArray()) }
            }
            start()
        }

    private fun earthGridService(
        builderFactory: () -> WebClient.Builder = { WebClient.builder() }
    ): EarthGridService {
        val builderProvider = mock(ObjectProvider::class.java) as ObjectProvider<WebClient.Builder>
        `when`(builderProvider.ifAvailable).thenReturn(builderFactory())

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