package space.nstart.pcp.pcp_mission_planing_service.service

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.read.ListAppender
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoInteractions
import org.mockito.Mockito.`when`
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.ObjectProvider
import space.nstart.pcp.pcp_mission_planing_service.entity.SurveyModeEntity
import space.nstart.pcp.pcp_types_lib.dto.routes.AngleRangeDto
import space.nstart.pcp.pcp_types_lib.dto.routes.RoutePassportDto
import space.nstart.pcp.pcp_types_lib.dto.routes.SurveyModeStatus
import space.nstart.pcp.pcp_types_lib.message.KafkaMessage
import space.nstart.pcp.pcp_types_lib.message.PcpKafkaEvent
import java.math.BigDecimal
import java.time.LocalDateTime
import java.util.UUID

class ModeStatusChangedKafkaListenerTest {

    private val objectMapper = jacksonObjectMapper().findAndRegisterModules()
    private val missionPlaningService = mock(MissionPlaningService::class.java)
    private val listener = ModeStatusChangedKafkaListener(
        objectMapperProvider = SingleObjectProvider(objectMapper),
        missionPlaningService = missionPlaningService,
    )

    @Test
    fun `consume finds mode and updates its status`() {
        val routePassport = routePassportDto(routeStatus = SurveyModeStatus.PROCESSED)
        val mode = SurveyModeEntity(id = 15L)
        val appender = attachAppender()
        val payload = objectMapper.writeValueAsString(
            KafkaMessage(
                type = PcpKafkaEvent.ModeStatusChangedEvent,
                data = routePassport,
            ).apply {
                source = "route-processing"
            }
        )
        `when`(missionPlaningService.findMode(routePassport)).thenReturn(mode)

        listener.consume(payload)

        verify(missionPlaningService).findMode(routePassport)
        verify(missionPlaningService).updateSurveyModeStatus(15L, SurveyModeStatus.PROCESSED)
        val infoLog = appender.list.last { it.level == Level.INFO }
        assertTrue(infoLog.formattedMessage.contains("Mode status changed event processed"))
        assertTrue(infoLog.formattedMessage.contains("surveyModeId=15"))
    }

    @Test
    fun `consume logs error when mode is not found`() {
        val routePassport = routePassportDto(routeStatus = SurveyModeStatus.FAILED)
        val appender = attachAppender()
        val payload = objectMapper.writeValueAsString(
            KafkaMessage(
                type = PcpKafkaEvent.ModeStatusChangedEvent,
                data = routePassport,
            ).apply {
                source = "route-processing"
            }
        )
        `when`(missionPlaningService.findMode(routePassport)).thenReturn(null)

        listener.consume(payload)

        verify(missionPlaningService).findMode(routePassport)
        verify(missionPlaningService, never()).updateSurveyModeStatus(15L, SurveyModeStatus.FAILED)
        val errorLog = appender.list.last { it.level == Level.ERROR }
        assertTrue(errorLog.formattedMessage.contains("Survey mode not found for ModeStatusChangedEvent"))
        assertTrue(errorLog.formattedMessage.contains("routeStatus=FAILED"))
    }

    @Test
    fun `consume ignores unsupported kafka event type`() {
        val appender = attachAppender()
        val payload = objectMapper.writeValueAsString(
            KafkaMessage(
                type = PcpKafkaEvent.RouteGeoRefEvent,
                data = routePassportDto(),
            )
        )

        listener.consume(payload)

        val debugLog = appender.list.last { it.level == Level.DEBUG }
        assertEquals("Ignoring Kafka message with unsupported type RouteGeoRefEvent", debugLog.formattedMessage)
        verifyNoInteractions(missionPlaningService)
    }

    private fun attachAppender(): ListAppender<ILoggingEvent> {
        val logger = LoggerFactory.getLogger(ModeStatusChangedKafkaListener::class.java) as Logger
        logger.detachAndStopAllAppenders()
        val appender = ListAppender<ILoggingEvent>()
        appender.start()
        logger.addAppender(appender)
        logger.level = Level.DEBUG
        return appender
    }

    private fun routePassportDto(routeStatus: SurveyModeStatus = SurveyModeStatus.PROCESSED): RoutePassportDto =
        RoutePassportDto(
            routeId = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
            kaShort = "22",
            kaFull = "SATELLITE-22",
            routeNameFull = "ROUTE-001",
            routeNameShort = "R-001",
            orbitNumber = 42,
            orbitState = "ASC",
            intervalBegin = LocalDateTime.of(2026, 4, 3, 12, 0),
            intervalEnd = LocalDateTime.of(2026, 4, 3, 12, 5),
            rollAngle = AngleRangeDto(BigDecimal.ZERO, BigDecimal.ONE),
            visirAngle = AngleRangeDto(BigDecimal.ZERO, BigDecimal.ONE),
            resolutionRange = BigDecimal.ONE,
            resolutionAzimuth = BigDecimal.ONE,
            polarisation = listOf("VV"),
            processingLevel = "L1",
            geometry = "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
            routeStatus = routeStatus,
        )

    private class SingleObjectProvider<T : Any>(private val value: T) : ObjectProvider<T> {
        override fun getObject(vararg args: Any?): T = value
        override fun getIfAvailable(): T = value
        override fun getIfUnique(): T = value
        override fun getObject(): T = value
    }
}
