package space.nstart.pcp.slots_service.service

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.ObjectProvider
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.client.ClientResponse
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
import java.net.URI
import java.util.UUID
import kotlin.test.assertEquals
import kotlin.test.assertFalse

class EarthServiceTest {
    @Test
    fun `reqs calls v1 requests list endpoint`() {
        val requestId = UUID.fromString("00000000-0000-0000-0000-000000000001")
        val requestedUris = mutableListOf<URI>()
        val service = earthService(
            requestedUris = requestedUris,
            responses = mutableListOf(
                requestsPageResponse(requestId),
                requestDetailsResponse(requestId),
            )
        )

        service.reqs().collectList().block()

        assertEquals("/v1/requests", requestedUris.first().path)
        assertEquals("page=0&size=500", requestedUris.first().query)
        assertFalse(requestedUris.any { uri -> uri.path == "/api/requests" })
    }

    @Test
    fun `reqs unwraps list items and maps request details to current request dto`() {
        val requestId = UUID.fromString("00000000-0000-0000-0000-000000000002")
        val service = earthService(
            responses = mutableListOf(
                requestsPageResponse(requestId, importance = 3.0),
                requestDetailsResponse(
                    id = requestId,
                    importance = 7.5,
                    geometry = "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
                ),
            )
        )

        val request = service.reqs().collectList().block()!!.single()

        assertEquals(requestId, request.requestId)
        assertEquals("Full request", request.name)
        assertEquals(7.5, request.importance)
        assertEquals("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", request.contour)
    }

    @Test
    fun `reqs reads all pages from v1 requests`() {
        val firstRequestId = UUID.fromString("00000000-0000-0000-0000-000000000003")
        val secondRequestId = UUID.fromString("00000000-0000-0000-0000-000000000004")
        val requestedUris = mutableListOf<URI>()
        val service = earthService(
            requestedUris = requestedUris,
            responses = mutableListOf(
                requestsPageResponse(firstRequestId, page = 0, totalPages = 2),
                requestsPageResponse(secondRequestId, page = 1, totalPages = 2),
                requestDetailsResponse(firstRequestId, geometry = "POLYGON EMPTY"),
                requestDetailsResponse(secondRequestId, geometry = "POLYGON EMPTY"),
            )
        )

        val requests = service.reqs().collectList().block()!!

        assertEquals(listOf(firstRequestId, secondRequestId), requests.map { request -> request.requestId })
        assertEquals(
            listOf("page=0&size=500", "page=1&size=500"),
            requestedUris.filter { uri -> uri.path == "/v1/requests" }.map { uri -> uri.query },
        )
    }

    @Test
    fun `reqs loads full request details for contour`() {
        val requestId = UUID.fromString("00000000-0000-0000-0000-000000000005")
        val requestedUris = mutableListOf<URI>()
        val service = earthService(
            requestedUris = requestedUris,
            responses = mutableListOf(
                requestsPageResponse(requestId, name = "Summary request"),
                requestDetailsResponse(
                    id = requestId,
                    name = "Detailed request",
                    geometry = "POLYGON ((10 10, 11 10, 11 11, 10 11, 10 10))",
                ),
            )
        )

        val request = service.reqs().collectList().block()!!.single()

        assertEquals("/v1/requests/$requestId", requestedUris[1].path)
        assertEquals("Detailed request", request.name)
        assertEquals("POLYGON ((10 10, 11 10, 11 11, 10 11, 10 10))", request.contour)
    }

    @Test
    fun `reqcells calls v1 request with cells and maps request and cell contours`() {
        val requestId = UUID.fromString("00000000-0000-0000-0000-000000000006")
        val requestedUris = mutableListOf<URI>()
        val service = earthService(
            requestedUris = requestedUris,
            responses = mutableListOf(
                requestWithCellsResponse(requestId),
            )
        )

        val response = service.reqcells(requestId.toString())!!

        assertEquals("/v1/requests/$requestId/cells", requestedUris.single().path)
        assertFalse(requestedUris.any { uri -> uri.path.contains("/api/requests/by-number") })
        assertEquals(requestId, response.request.requestId)
        assertEquals("Request with cells", response.request.name)
        assertEquals("POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))", response.request.contour)
        assertEquals(123, response.cells.single().id)
        assertEquals(123, response.cells.single().num)
        assertEquals(10.0, response.cells.single().importance)
        assertEquals("POLYGON ((1 1, 2 1, 2 2, 1 2, 1 1))", response.cells.single().contour)
    }

    @Test
    fun `cells calls v1 cells with min importance and maps wrapper items`() {
        val requestedUris = mutableListOf<URI>()
        val service = earthService(
            requestedUris = requestedUris,
            responses = mutableListOf(
                cellsPageResponse(),
            )
        )

        val cells = service.cells(4.5).toList()

        assertEquals("/v1/cells", requestedUris.single().path)
        assertEquals("minImportance=4.5&page=0&size=500", requestedUris.single().query)
        assertFalse(requestedUris.any { uri -> uri.path == "/api/earth-grid/by-importance" })
        assertFalse(requestedUris.any { uri -> uri.query?.contains("countLat") == true })
        assertFalse(requestedUris.any { uri -> uri.query?.contains("countLong") == true })
        assertEquals(1, cells.single().id)
        assertEquals(1, cells.single().num)
        assertEquals(55.5, cells.single().latitude)
        assertEquals(37.5, cells.single().longitude)
        assertEquals(4.5, cells.single().importance)
        assertEquals("POLYGON ((30 50, 31 50, 31 51, 30 51, 30 50))", cells.single().contour)
    }

    @Test
    fun `cells reads all pages from v1 cells`() {
        val requestedUris = mutableListOf<URI>()
        val service = earthService(
            requestedUris = requestedUris,
            responses = mutableListOf(
                cellsPageResponse(cellNum = 1, page = 0, totalPages = 2),
                cellsPageResponse(cellNum = 2, page = 1, totalPages = 2),
            )
        )

        val cells = service.cells(9.0).toList()

        assertEquals(listOf(1L, 2L), cells.map { cell -> cell.num })
        assertEquals(
            listOf("minImportance=9.0&page=0&size=500", "minImportance=9.0&page=1&size=500"),
            requestedUris.filter { uri -> uri.path == "/v1/cells" }.map { uri -> uri.query },
        )
    }

    private fun earthService(
        requestedUris: MutableList<URI> = mutableListOf(),
        responses: MutableList<ClientResponse>,
    ): EarthService {
        val builder = WebClient.builder()
            .exchangeFunction { request ->
                requestedUris += request.url()
                Mono.just(responses.removeAt(0))
            }
        return EarthService(provider(builder), url = "http://pcp-request-service")
    }

    private fun requestsPageResponse(
        id: UUID,
        page: Int = 0,
        totalPages: Int = 1,
        importance: Double = 1.0,
        name: String = "Summary request",
    ): ClientResponse =
        jsonResponse(
            """
            {
              "items": [
                {
                  "id": "$id",
                  "name": "$name",
                  "status": "ACTIVE",
                  "surveyType": "OPTICS",
                  "importance": $importance,
                  "beginDateTime": "2026-01-01T00:00:00Z",
                  "endDateTime": "2026-01-31T00:00:00Z",
                  "kpp": [1],
                  "highPriorityTransmit": false,
                  "coveragePercent": 0.0,
                  "createdAt": "2026-01-01T00:00:00Z",
                  "updatedAt": "2026-01-01T00:00:00Z"
                }
              ],
              "page": $page,
              "size": 500,
              "totalItems": $totalPages,
              "totalPages": $totalPages
            }
            """.trimIndent()
        )

    private fun requestDetailsResponse(
        id: UUID,
        name: String = "Full request",
        importance: Double = 1.0,
        geometry: String = "POLYGON EMPTY",
    ): ClientResponse =
        jsonResponse(
            """
            {
              "id": "$id",
              "name": "$name",
              "status": "ACTIVE",
              "surveyType": "OPTICS",
              "geometry": "$geometry",
              "importance": $importance,
              "beginDateTime": "2026-01-01T00:00:00Z",
              "endDateTime": "2026-01-31T00:00:00Z",
              "kpp": [1],
              "highPriorityTransmit": false,
              "coverage": {"currentPercent": 0.0},
              "createdAt": "2026-01-01T00:00:00Z",
              "updatedAt": "2026-01-01T00:00:00Z"
            }
            """.trimIndent()
        )

    private fun requestWithCellsResponse(id: UUID): ClientResponse =
        jsonResponse(
            """
            {
              "request": {
                "id": "$id",
                "name": "Request with cells",
                "status": "ACTIVE",
                "surveyType": "OPTICS",
                "geometry": "POLYGON ((0 0, 5 0, 5 5, 0 5, 0 0))",
                "importance": 7.0,
                "beginDateTime": "2026-01-01T00:00:00Z",
                "endDateTime": "2026-01-31T00:00:00Z",
                "kpp": [1],
                "highPriorityTransmit": false,
                "coverage": {"currentPercent": 0.0},
                "createdAt": "2026-01-01T00:00:00Z",
                "updatedAt": "2026-01-01T00:00:00Z"
              },
              "cells": [
                {
                  "cellNum": 123,
                  "coveragePercent": 42.5,
                  "importance": 10.0,
                  "contour": "POLYGON ((1 1, 2 1, 2 2, 1 2, 1 1))"
                }
              ]
            }
            """.trimIndent()
        )

    private fun cellsPageResponse(
        cellNum: Long = 1,
        page: Int = 0,
        totalPages: Int = 1,
        importance: Double = 4.5,
    ): ClientResponse =
        jsonResponse(
            """
            {
              "items": [
                {
                  "cellNum": $cellNum,
                  "latitude": 55.5,
                  "longitude": 37.5,
                  "importance": $importance,
                  "contour": "POLYGON ((30 50, 31 50, 31 51, 30 51, 30 50))"
                }
              ],
              "page": $page,
              "size": 500,
              "totalItems": $totalPages,
              "totalPages": $totalPages
            }
            """.trimIndent()
        )

    private fun jsonResponse(body: String): ClientResponse =
        ClientResponse.create(HttpStatus.OK)
            .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .body(body)
            .build()

    private fun provider(builder: WebClient.Builder): ObjectProvider<WebClient.Builder> =
        object : ObjectProvider<WebClient.Builder> {
            override fun getObject(vararg args: Any?): WebClient.Builder = builder
            override fun getObject(): WebClient.Builder = builder
            override fun getIfAvailable(): WebClient.Builder = builder
            override fun getIfUnique(): WebClient.Builder = builder
            override fun iterator(): MutableIterator<WebClient.Builder> = mutableListOf(builder).iterator()
        }
}
