package space.nstart.pcp.routepassportconsumer.listener

import io.micrometer.core.instrument.simple.SimpleMeterRegistry
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test
import org.mockito.Mockito.doThrow
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoInteractions
import org.mockito.Mockito.`when`
import org.springframework.kafka.support.Acknowledgment
import space.nstart.pcp.pcp_types_lib.dto.routes.AngleRangeDto
import space.nstart.pcp.pcp_types_lib.dto.routes.ParsedRoutePassportMessage
import space.nstart.pcp.pcp_types_lib.dto.routes.RoutePassportDto
import space.nstart.pcp.pcp_types_lib.dto.routes.RoutePassportMessageMetadata
import space.nstart.pcp.routepassportconsumer.config.KafkaTopicsProperties
import space.nstart.pcp.routepassportconsumer.service.RoutePassportIngestionService
import space.nstart.pcp.routepassportconsumer.service.RoutePassportJsonParserService
import space.nstart.pcp.routepassportconsumer.service.RoutePassportMetrics
import space.nstart.pcp.routepassportconsumer.service.RoutePassportSourceRecordMetadata
import java.math.BigDecimal
import java.time.LocalDateTime
import java.util.UUID

class RoutePassportMessageListenerTest {

    @Test
    fun `should parse incoming record, ingest it and acknowledge offset`() {
        val parserService = mock(RoutePassportJsonParserService::class.java)
        val ingestionService = mock(RoutePassportIngestionService::class.java)
        val acknowledgment = mock(Acknowledgment::class.java)
        val listener = RoutePassportMessageListener(
            routePassportJsonParserService = parserService,
            routePassportIngestionService = ingestionService,
            routePassportMetrics = routePassportMetrics(),
            kafkaTopicsProperties = kafkaTopicsProperties(),
        )
        val rawPayload = """{"id":"route-1"}"""
        val record = consumerRecord(rawPayload)
        val parsedMessage = parsedMessage()
        val sourceRecord = sourceRecordMetadata(record)

        `when`(parserService.parse(rawPayload)).thenReturn(parsedMessage)

        listener.listen(record, acknowledgment)

        verify(parserService).parse(rawPayload)
        verify(ingestionService).ingest(
            parsedMessage = parsedMessage,
            rawPayload = rawPayload,
            sourceRecord = sourceRecord,
        )
        verify(acknowledgment, times(1)).acknowledge()
    }

    @Test
    fun `should not ingest or acknowledge when parser throws exception`() {
        val parserService = mock(RoutePassportJsonParserService::class.java)
        val ingestionService = mock(RoutePassportIngestionService::class.java)
        val acknowledgment = mock(Acknowledgment::class.java)
        val listener = RoutePassportMessageListener(
            routePassportJsonParserService = parserService,
            routePassportIngestionService = ingestionService,
            routePassportMetrics = routePassportMetrics(),
            kafkaTopicsProperties = kafkaTopicsProperties(),
        )
        val rawPayload = """{"id":"route-1"}"""
        val record = consumerRecord(rawPayload)
        val exception = IllegalArgumentException("payload is invalid")

        `when`(parserService.parse(rawPayload)).thenThrow(exception)

        assertThrows(IllegalArgumentException::class.java) {
            listener.listen(record, acknowledgment)
        }

        verify(parserService).parse(rawPayload)
        verifyNoInteractions(ingestionService)
        verify(acknowledgment, never()).acknowledge()
    }

    @Test
    fun `should not acknowledge when ingestion throws exception`() {
        val parserService = mock(RoutePassportJsonParserService::class.java)
        val ingestionService = mock(RoutePassportIngestionService::class.java)
        val acknowledgment = mock(Acknowledgment::class.java)
        val listener = RoutePassportMessageListener(
            routePassportJsonParserService = parserService,
            routePassportIngestionService = ingestionService,
            routePassportMetrics = routePassportMetrics(),
            kafkaTopicsProperties = kafkaTopicsProperties(),
        )
        val rawPayload = """{"id":"route-1"}"""
        val record = consumerRecord(rawPayload)
        val parsedMessage = parsedMessage()
        val sourceRecord = sourceRecordMetadata(record)
        val exception = IllegalStateException("database is unavailable")

        `when`(parserService.parse(rawPayload)).thenReturn(parsedMessage)
        doThrow(exception).`when`(ingestionService).ingest(
            parsedMessage = parsedMessage,
            rawPayload = rawPayload,
            sourceRecord = sourceRecord,
        )

        assertThrows(IllegalStateException::class.java) {
            listener.listen(record, acknowledgment)
        }

        verify(parserService).parse(rawPayload)
        verify(ingestionService).ingest(
            parsedMessage = parsedMessage,
            rawPayload = rawPayload,
            sourceRecord = sourceRecord,
        )
        verify(acknowledgment, never()).acknowledge()
    }

    private fun kafkaTopicsProperties(): KafkaTopicsProperties =
        KafkaTopicsProperties(
            input = INPUT_TOPIC,
            processedRoute = "pcp.route.in.v1",
            routeGeoreference = "pcp.route.georeference.v1",
        )

    private fun routePassportMetrics(): RoutePassportMetrics =
        RoutePassportMetrics(SimpleMeterRegistry())

    private fun consumerRecord(rawPayload: String): ConsumerRecord<String, String> =
        ConsumerRecord(
            INPUT_TOPIC,
            3,
            42L,
            "route-key",
            rawPayload,
        )

    private fun sourceRecordMetadata(record: ConsumerRecord<String, String>): RoutePassportSourceRecordMetadata =
        RoutePassportSourceRecordMetadata(
            topic = record.topic(),
            partition = record.partition(),
            offset = record.offset(),
            key = record.key(),
        )

    private fun parsedMessage(): ParsedRoutePassportMessage =
        ParsedRoutePassportMessage(
            metadata = RoutePassportMessageMetadata(
                traceId = "trace-1",
                correlationId = "corr-1",
            ),
            routePassportDto = RoutePassportDto(
                routeId = UUID.fromString("51b07d12-1823-4bd5-b55d-de02a5304c03"),
                kaShort = "S1A",
                kaFull = "sentinel-1a",
                routeNameFull = "ROUTE-001",
                routeNameShort = "S1A-GRDH",
                orbitNumber = 59796L,
                orbitState = "Descending",
                intervalBegin = LocalDateTime.of(2026, 4, 1, 10, 15, 0),
                intervalEnd = LocalDateTime.of(2026, 4, 1, 10, 16, 30),
                rollAngle = AngleRangeDto(
                    min = BigDecimal("-15.5"),
                    max = BigDecimal("-12.0"),
                ),
                visirAngle = AngleRangeDto(
                    min = BigDecimal("21.0"),
                    max = BigDecimal("24.5"),
                ),
                resolutionRange = BigDecimal("10.0"),
                resolutionAzimuth = BigDecimal("10.0"),
                polarisation = listOf("VH", "VV"),
                processingLevel = "GRD",
                geometry = "POLYGON((0 0, 1 0, 1 1, 0 0))",
            ),
        )

    private companion object {
        const val INPUT_TOPIC = "pcp.request.survey-georeference.v1"
    }
}
