package space.nstart.pcp_tgu_service.service

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.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import space.nstart.pcp_tgu_service.entity.SpacecraftVisibilitySnapshotEntity
import space.nstart.pcp_tgu_service.integration.api.VisibilityPayloadParser
import space.nstart.pcp_tgu_service.repository.SpacecraftVisibilitySnapshotRepository
import java.security.MessageDigest
import java.time.LocalDateTime
import java.util.Optional

class VisibilityFlowTest {

    @Test
    fun `parser builds spacecraft points from raw payload and keeps station ordering`() {
        val parser = parser()

        val points = parser.parse("25544", rawPayload(stationId = 9) + rawPayloadTail(stationId = 7))

        assertEquals("25544", points.spacecraftId)
        assertEquals(listOf("7", "9"), points.points.map { point -> point.pointId })
        // Парсер трактует UTC-инстант баллистики как UTC-стенку (naive LocalDateTime = UTC), без сдвига на пояс JVM.
        assertEquals(
            LocalDateTime.parse("2026-05-26T10:00:00"),
            points.points[1].observationWindows.single().startTime
        )
    }

    @Test
    fun `same visibility payload updates fetchedAt and persists snapshot`() {
        val repository = mock(SpacecraftVisibilitySnapshotRepository::class.java)
        val payload = rawPayloadArray(stationId = 7)
        val existing = SpacecraftVisibilitySnapshotEntity(
            spacecraftId = "25544",
            noradId = 25544L,
            payloadJson = payload,
            payloadHash = sha256(payload),
            fetchedAt = LocalDateTime.of(2026, 5, 26, 10, 0)
        )
        `when`(repository.findById("25544")).thenReturn(Optional.of(existing))
        `when`(repository.save(anyNonNull())).thenAnswer { invocation -> invocation.arguments[0] }
        val service = SpacecraftVisibilitySnapshotService(repository, parser())

        service.saveSnapshot("25544", 25544L, payload, LocalDateTime.of(2026, 5, 26, 11, 0))

        assertEquals(LocalDateTime.of(2026, 5, 26, 11, 0), existing.fetchedAt)
        verify(repository, times(1)).save(anyNonNull())
    }

    @Test
    fun `new visibility payload stores raw snapshot`() {
        val repository = mock(SpacecraftVisibilitySnapshotRepository::class.java)
        `when`(repository.findById("25544")).thenReturn(Optional.empty())
        `when`(repository.save(anyNonNull())).thenAnswer { invocation -> invocation.arguments[0] }
        val service = SpacecraftVisibilitySnapshotService(repository, parser())
        val payload = rawPayloadArray(stationId = 7)

        service.saveSnapshot("25544", 25544L, payload, LocalDateTime.of(2026, 5, 26, 11, 0))

        verify(repository, times(1)).save(anyNonNull())
    }

    private fun parser(): VisibilityPayloadParser =
        VisibilityPayloadParser(jacksonObjectMapper().findAndRegisterModules())

    private fun rawPayloadArray(stationId: Long): String =
        rawPayload(stationId).plus("]")

    private fun rawPayload(stationId: Long): String =
        """[{"noradId":25544,"stationId":$stationId,"revolution":1,"onStart":{"time":"2026-05-26T10:00:00Z","elevation":1.0},"onMaximum":{"time":"2026-05-26T10:05:00Z","elevation":45.0},"onStop":{"time":"2026-05-26T10:10:00Z","elevation":1.0}}"""

    private fun rawPayloadTail(stationId: Long): String =
        """,{"noradId":25544,"stationId":$stationId,"revolution":1,"onStart":{"time":"2026-05-26T11:00:00Z","elevation":1.0},"onMaximum":{"time":"2026-05-26T11:05:00Z","elevation":45.0},"onStop":{"time":"2026-05-26T11:10:00Z","elevation":1.0}}]"""

    private fun sha256(value: String): String =
        MessageDigest.getInstance("SHA-256")
            .digest(value.toByteArray(Charsets.UTF_8))
            .joinToString(separator = "") { byte -> "%02x".format(byte) }

    @Suppress("UNCHECKED_CAST")
    private fun <T> anyNonNull(): T {
        org.mockito.ArgumentMatchers.any<T>()
        return null as T
    }
}
