package space.nstart.pcp.routepassportconsumer.service

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test
import space.nstart.pcp.pcp_types_lib.dto.routes.RawGeoJsonGeometryDto
import space.nstart.pcp.pcp_types_lib.dto.routes.SurveyModeStatus
import space.nstart.pcp.routepassportconsumer.exception.RoutePassportContractException
import java.util.UUID
import tools.jackson.databind.ObjectMapper
import tools.jackson.databind.json.JsonMapper
import tools.jackson.module.kotlin.KotlinModule
import java.nio.charset.StandardCharsets

/**
 * Тест сервиса парсинга JSON паспорта маршрута.
 */
class RoutePassportJsonParserServiceTest {
    private val objectMapper: ObjectMapper = JsonMapper.builder()
        .addModule(KotlinModule.Builder().build())
        .build()

    @Test
    fun `should parse envelope passport json into dto`() {
        val parserService = RoutePassportJsonParserService(objectMapper, RouteGeometryConverter())

        val json: String = this::class.java.classLoader
            .getResourceAsStream("test-passport-message.json")
            ?.readAllBytes()
            ?.toString(StandardCharsets.UTF_8)
            ?: error("Не найден файл test-passport-message.json в classpath")

        val parsedMessage = parserService.parse(json)
        val dto = parsedMessage.routePassportDto

        assertEquals("c0dd08ea-dd64-4633-8845-d1b163bdf506", parsedMessage.metadata.traceId)
        assertEquals("db605d5f-77e9-45b4-82cc-709837306257", parsedMessage.metadata.correlationId)
        assertEquals(UUID.fromString("51b07d12-1823-4bd5-b55d-de02a5304c03"), dto.routeId)
        assertEquals("S1A", dto.kaShort)
        assertEquals("sentinel-1a", dto.kaFull)
        assertEquals("S1A-GRDH", dto.routeNameShort)
        assertEquals(
            "S1A_IW_GRDH_1SDV_20250625T013854_20250625T013919_059796_076D15_B5C1.SAFE",
            dto.routeNameFull,
        )
        assertEquals(59796L, dto.orbitNumber)
        assertEquals("Descending", dto.orbitState)
        assertEquals("GRD", dto.processingLevel)
        assertEquals(SurveyModeStatus.PROCESSED, dto.routeStatus)
        assertEquals(listOf("VH", "VV"), dto.polarisation)
        assertEquals(
            "POLYGON((67.675827 38.114815, 64.779434 38.514816, 65.094017 40.015545, 68.05294 39.616795, 67.675827 38.114815))",
            dto.geometry,
        )
    }

    @Test
    fun `should generate route id when raw payload does not contain it`() {
        val parserService = RoutePassportJsonParserService(objectMapper, RouteGeometryConverter())

        val json = """
            {
              "traceid": "trace-1",
              "correlationid": "corr-1",
              "data": {
                "id": "ROUTE-001",
                "collection": "S1A-GRDH",
                "geometry": {
                  "type": "Polygon",
                  "coordinates": [[[0, 0], [1, 0], [1, 1], [0, 0]]]
                },
                "properties": {
                  "platform": "sentinel-1a",
                  "missionId": "S1A",
                  "route:orbit_number": "59796",
                  "sat:orbit_state": "Descending",
                  "intervalBegin": "2025-06-25T01:38:54.481190",
                  "intervalEnd": "2025-06-25T01:39:19.479675",
                  "rollAngle": [-5.0, 12.0],
                  "visirAngle": [30.0, 46.0],
                  "resolutionRange": "1.000000e+01",
                  "resolutionAzimuth": "1.000000e+01",
                  "polarisation": ["VH", "VV"],
                  "processing:level": "GRD"
                }
              }
            }
        """.trimIndent()

        val parsedMessage = parserService.parse(json)

        assertEquals(
            UUID.nameUUIDFromBytes("ROUTE-001".toByteArray(StandardCharsets.UTF_8)),
            parsedMessage.routePassportDto.routeId,
        )
    }

    @Test
    fun `should parse trace_id alias without changing correlationid`() {
        val parserService = RoutePassportJsonParserService(objectMapper, RouteGeometryConverter())

        val json = """
            {
              "trace_id": "trace-aliased",
              "correlationid": "corr-aliased",
              "data": {
                "id": "ROUTE-001",
                "collection": "S1A-GRDH",
                "geometry": {
                  "type": "Polygon",
                  "coordinates": [[[0, 0], [1, 0], [1, 1], [0, 0]]]
                },
                "properties": {
                  "platform": "sentinel-1a",
                  "missionId": "S1A",
                  "route:orbit_number": "59796",
                  "sat:orbit_state": "Descending",
                  "intervalBegin": "2025-06-25T01:38:54.481190",
                  "intervalEnd": "2025-06-25T01:39:19.479675",
                  "rollAngle": [-5.0, 12.0],
                  "visirAngle": [30.0, 46.0],
                  "resolutionRange": "1.000000e+01",
                  "resolutionAzimuth": "1.000000e+01",
                  "polarisation": ["VH", "VV"],
                  "processing:level": "GRD"
                }
              }
            }
        """.trimIndent()

        val parsedMessage = parserService.parse(json)

        assertEquals("trace-aliased", parsedMessage.metadata.traceId)
        assertEquals("corr-aliased", parsedMessage.metadata.correlationId)
    }

    @Test
    fun `should take geometry from converter`() {
        val routeGeometryConverter = StubRouteGeometryConverter()
        val parserService = RoutePassportJsonParserService(objectMapper, routeGeometryConverter)

        val parsedMessage = parserService.parse(minimalJson("Polygon", "[[[0, 0], [1, 0], [1, 1], [0, 0]]]"))

        assertEquals("POLYGON((9 9, 10 9, 10 10, 9 9))", parsedMessage.routePassportDto.geometry)
        assertEquals(1, routeGeometryConverter.callCount)
    }

    @Test
    fun `should reject unsupported geometry type in parser path`() {
        val parserService = RoutePassportJsonParserService(objectMapper, RouteGeometryConverter())

        assertThrows(RoutePassportContractException::class.java) {
            parserService.parse(minimalJson("Point", "[0, 0]"))
        }
    }

    private fun minimalJson(
        geometryType: String,
        coordinates: String,
    ): String =
        """
            {
              "traceid": "trace-1",
              "correlationid": "corr-1",
              "data": {
                "id": "ROUTE-001",
                "collection": "S1A-GRDH",
                "geometry": {
                  "type": "$geometryType",
                  "coordinates": $coordinates
                },
                "properties": {
                  "platform": "sentinel-1a",
                  "missionId": "S1A",
                  "route:orbit_number": "59796",
                  "sat:orbit_state": "Descending",
                  "intervalBegin": "2025-06-25T01:38:54.481190",
                  "intervalEnd": "2025-06-25T01:39:19.479675",
                  "rollAngle": [-5.0, 12.0],
                  "visirAngle": [30.0, 46.0],
                  "resolutionRange": "1.000000e+01",
                  "resolutionAzimuth": "1.000000e+01",
                  "polarisation": ["VH", "VV"],
                  "processing:level": "GRD"
                }
              }
            }
        """.trimIndent()

    private class StubRouteGeometryConverter : RouteGeometryConverter() {
        var callCount: Int = 0

        override fun convert(geometry: RawGeoJsonGeometryDto?): String {
            callCount += 1
            return "POLYGON((9 9, 10 9, 10 10, 9 9))"
        }
    }
}
