package space.nstart.pcp.pcp_request_service.repository

import jakarta.persistence.EntityManager
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.kafka.common.serialization.StringDeserializer
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory
import org.springframework.kafka.core.ConsumerFactory
import org.springframework.kafka.core.DefaultKafkaConsumerFactory
import org.springframework.kafka.listener.adapter.RecordFilterStrategy
import org.springframework.transaction.annotation.Transactional
import space.nstart.pcp.pcp_request_service.entity.PDCMEntity
import space.nstart.pcp.pcp_request_service.entity.SatelliteInitialConditionEntity
import space.nstart.pcp.pcp_types_lib.dto.ballistics.InitialConditionsDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.MovementModel
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
import java.time.LocalDateTime
import kotlin.test.assertEquals

@SpringBootTest(
    properties = [
        "spring.cloud.config.enabled=false",
        "spring.cloud.config.import-check.enabled=false",
        "spring.kafka.bootstrap-servers=false",
        "spring.kafka.listener.auto-startup=false",
        "spring.datasource.driver-class-name=org.h2.Driver",
        "spring.datasource.url=jdbc:h2:mem:pdcm-repository-test;MODE=PostgreSQL;DB_CLOSE_DELAY=-1",
        "spring.datasource.username=sa",
        "spring.datasource.password=",
        "spring.flyway.enabled=false",
        "spring.jpa.hibernate.ddl-auto=create-drop",
        "spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect"
    ]
)
@Transactional
class PDCMRepositoryTest {

    @Autowired
    private lateinit var entityManager: EntityManager

    @Autowired
    private lateinit var repository: PDCMRepository

    @Autowired
    private lateinit var satelliteInitialConditionRepository: SatelliteInitialConditionRepository

    @Test
    fun `find satellite orbit availability returns all satellites with points when time is null`() {
        persistPoint(11L, LocalDateTime.of(2026, 4, 14, 10, 0))
        persistPoint(11L, LocalDateTime.of(2026, 4, 14, 12, 0))
        persistPoint(22L, LocalDateTime.of(2026, 4, 15, 9, 30))
        persistPoint(22L, LocalDateTime.of(2026, 4, 15, 18, 45))

        val actual = repository.findSatelliteOrbitAvailability()

        assertEquals(2, actual.size)
        assertEquals(11L, actual[0].satelliteId)
        assertEquals(LocalDateTime.of(2026, 4, 14, 10, 0), actual[0].timeStart)
        assertEquals(LocalDateTime.of(2026, 4, 14, 12, 0), actual[0].timeStop)
        assertEquals(22L, actual[1].satelliteId)
        assertEquals(LocalDateTime.of(2026, 4, 15, 9, 30), actual[1].timeStart)
        assertEquals(LocalDateTime.of(2026, 4, 15, 18, 45), actual[1].timeStop)
    }

    @Test
    fun `find satellite orbit availability filters satellites whose interval contains requested time`() {
        persistPoint(11L, LocalDateTime.of(2026, 4, 14, 10, 0))
        persistPoint(11L, LocalDateTime.of(2026, 4, 14, 12, 0))
        persistPoint(22L, LocalDateTime.of(2026, 4, 15, 9, 30))
        persistPoint(22L, LocalDateTime.of(2026, 4, 15, 18, 45))

        val actual = repository.findSatelliteOrbitAvailabilityAtTime(LocalDateTime.of(2026, 4, 14, 11, 0))

        assertEquals(1, actual.size)
        assertEquals(11L, actual.single().satelliteId)
        assertEquals(LocalDateTime.of(2026, 4, 14, 10, 0), actual.single().timeStart)
        assertEquals(LocalDateTime.of(2026, 4, 14, 12, 0), actual.single().timeStop)
    }

    @Test
    fun `find satellite orbit availability for date returns satellites with points on requested date`() {
        persistPoint(11L, LocalDateTime.of(2026, 4, 14, 10, 0))
        persistPoint(11L, LocalDateTime.of(2026, 4, 14, 12, 0))
        persistPoint(22L, LocalDateTime.of(2026, 4, 14, 23, 30))
        persistPoint(33L, LocalDateTime.of(2026, 4, 15, 9, 30))

        val actual = repository.findSatelliteOrbitAvailabilityForDate(
            LocalDateTime.of(2026, 4, 14, 0, 0),
            LocalDateTime.of(2026, 4, 15, 0, 0)
        )

        assertEquals(listOf(11L, 22L), actual.map { it.satelliteId })
        assertEquals(LocalDateTime.of(2026, 4, 14, 10, 0), actual[0].timeStart)
        assertEquals(LocalDateTime.of(2026, 4, 14, 12, 0), actual[0].timeStop)
        assertEquals(LocalDateTime.of(2026, 4, 14, 23, 30), actual[1].timeStart)
        assertEquals(LocalDateTime.of(2026, 4, 14, 23, 30), actual[1].timeStop)
    }

    @Test
    fun `satellite initial conditions repository returns last calculated condition`() {
        val oldCondition = satelliteInitialConditionRepository.save(sampleInitialCondition(11L, 1L))
        oldCondition.markLastCalculated(LocalDateTime.of(2026, 4, 14, 10, 0))
        satelliteInitialConditionRepository.save(oldCondition)

        val newCondition = satelliteInitialConditionRepository.save(sampleInitialCondition(11L, 2L))
        satelliteInitialConditionRepository.clearLastCalculated(11L)
        newCondition.markLastCalculated(LocalDateTime.of(2026, 4, 14, 12, 0))
        satelliteInitialConditionRepository.save(newCondition)
        satelliteInitialConditionRepository.save(sampleInitialCondition(11L, 3L))

        entityManager.flush()
        entityManager.clear()

        val actual = satelliteInitialConditionRepository
            .findFirstBySatelliteIdAndLastCalculatedTrueOrderByCalculatedAtDescIdDesc(11L)

        assertEquals(2L, actual?.toDto()?.ic?.orbPoint?.revolution)
        assertEquals(MovementModel.KONDOR, actual?.movementModel)
        assertEquals(
            1,
            satelliteInitialConditionRepository.findAll().count { entity ->
                entity.satelliteId == 11L && entity.lastCalculated
            }
        )
    }

    private fun persistPoint(satelliteId: Long, time: LocalDateTime) {
        entityManager.persist(
            PDCMEntity(
                satelliteId = satelliteId,
                time = time,
                revolution = 1,
                x = 1.0,
                y = 2.0,
                z = 3.0,
                vx = 4.0,
                vy = 5.0,
                vz = 6.0
            )
        )
        entityManager.flush()
    }

    private fun sampleInitialCondition(satelliteId: Long, revolution: Long) =
        SatelliteInitialConditionEntity(
            SatelliteICDTO(
                satelliteId = satelliteId,
                movementModel = MovementModel.KONDOR,
                ic = InitialConditionsDTO(
                    orbPoint = OrbPointDTO(
                        time = LocalDateTime.of(2026, 4, 14, 10, 0).plusHours(revolution),
                        revolution = revolution,
                        vx = 1.0,
                        vy = 2.0,
                        vz = 3.0,
                        x = 4.0,
                        y = 5.0,
                        z = 6.0
                    ),
                    sBall = 0.2,
                    f81 = 141.0
                )
            )
        )

    @TestConfiguration
    class KafkaFilterStubConfiguration {
        @Bean("kafkaListenerContainerFactory")
        fun kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory<String, String> =
            ConcurrentKafkaListenerContainerFactory<String, String>().apply {
                setConsumerFactory(testConsumerFactory())
                setAutoStartup(false)
            }

        private fun testConsumerFactory(): ConsumerFactory<String, String> =
            DefaultKafkaConsumerFactory(
                mapOf(
                    ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to "localhost:9092",
                    ConsumerConfig.GROUP_ID_CONFIG to "test",
                    ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java,
                    ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java
                )
            )

        @Bean("placedIcFilter")
        fun placedIcFilter(): RecordFilterStrategy<String, String> = RecordFilterStrategy { false }

        @Bean("placedIcRvFilter")
        fun placedIcRvFilter(): RecordFilterStrategy<String, String> = RecordFilterStrategy { false }

        @Bean("updatedIcFilter")
        fun updatedIcFilter(): RecordFilterStrategy<String, String> = RecordFilterStrategy { false }

        @Bean("satelliteDeletedFilter")
        fun satelliteDeletedFilter(): RecordFilterStrategy<String, String> = RecordFilterStrategy { false }
    }
}
