package space.nstart.pcp_tgu_service.service

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.`when`
import space.nstart.pcp_tgu_service.domain.CalculatedPlan
import space.nstart.pcp_tgu_service.entity.PlannedPlanEntity
import space.nstart.pcp_tgu_service.entity.PlannedPlanStatus
import space.nstart.pcp_tgu_service.entity.SpacecraftSnapshotEntity
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
import space.nstart.pcp_tgu_service.repository.SpacecraftSnapshotRepository
import java.time.LocalDateTime
import java.util.UUID

class PlanQueryServiceTest {

    private val spacecraftId = "25544"

    @Test
    fun `getAllPlans merges persisted with projection and dedupes by business key`() {
        val persisted = plannedPlan(time(1), time(4), PlannedPlanStatus.AWAITING_LAYIN)
        // Та же бизнес-ключ-тройка, что у persisted — должна быть выкинута из проекции.
        val duplicateProjected = calculated(persisted.startTime, persisted.endTime)
        val freshProjected = calculated(time(5), time(8))

        val plannedRepository = mock(PlannedPlanRepository::class.java)
        `when`(plannedRepository.findAllByOrderByStartTime()).thenReturn(listOf(persisted))
        val snapshotRepository = mock(SpacecraftSnapshotRepository::class.java)
        `when`(snapshotRepository.findAllByActiveTrue()).thenReturn(
            listOf(SpacecraftSnapshotEntity(spacecraftId = spacecraftId, noradId = 25544L))
        )
        val projection = mock(PlanProjectionService::class.java)
        `when`(projection.project(spacecraftId)).thenReturn(listOf(duplicateProjected, freshProjected))

        val service = PlanQueryService(plannedRepository, snapshotRepository, projection)
        val result = service.getAllPlans(historyDays = 7)

        assertEquals(2, result.size)
        val persistedResponse = result.first { it.startTime == persisted.startTime }
        assertEquals(PlannedPlanStatus.AWAITING_LAYIN, persistedResponse.status)
        val projectedResponse = result.first { it.startTime == freshProjected.startTime }
        assertEquals(PlannedPlanStatus.PLANNED, projectedResponse.status)
        // Отсортировано по startTime.
        assertTrue(result[0].startTime <= result[1].startTime)
    }

    @Test
    fun `getAllPlans drops persisted plans older than the history window`() {
        val old = plannedPlan(time(-240), time(-239), PlannedPlanStatus.ACCEPTED)
        val plannedRepository = mock(PlannedPlanRepository::class.java)
        `when`(plannedRepository.findAllByOrderByStartTime()).thenReturn(listOf(old))
        val snapshotRepository = mock(SpacecraftSnapshotRepository::class.java)
        `when`(snapshotRepository.findAllByActiveTrue()).thenReturn(emptyList())
        val projection = mock(PlanProjectionService::class.java)

        val service = PlanQueryService(plannedRepository, snapshotRepository, projection)
        val result = service.getAllPlans(historyDays = 7)

        assertTrue(result.isEmpty())
    }

    @Test
    fun `getAllPlans excludes DRAFT plans from the result`() {
        val draft = plannedPlan(time(2), time(5), PlannedPlanStatus.DRAFT)
        val active = plannedPlan(time(5), time(8), PlannedPlanStatus.AWAITING_LAYIN)
        val plannedRepository = mock(PlannedPlanRepository::class.java)
        `when`(plannedRepository.findAllByOrderByStartTime()).thenReturn(listOf(draft, active))
        val snapshotRepository = mock(SpacecraftSnapshotRepository::class.java)
        `when`(snapshotRepository.findAllByActiveTrue()).thenReturn(emptyList())
        val projection = mock(PlanProjectionService::class.java)

        val service = PlanQueryService(plannedRepository, snapshotRepository, projection)
        val result = service.getAllPlans(historyDays = 7)

        assertEquals(1, result.size)
        assertEquals(PlannedPlanStatus.AWAITING_LAYIN, result.single().status)
    }

    @Test
    fun `getDraftPlans returns only DRAFT plans`() {
        val draft = plannedPlan(time(2), time(5), PlannedPlanStatus.DRAFT)
        val active = plannedPlan(time(5), time(8), PlannedPlanStatus.AWAITING_LAYIN)
        val plannedRepository = mock(PlannedPlanRepository::class.java)
        `when`(plannedRepository.findAllByOrderByStartTime()).thenReturn(listOf(draft, active))
        val snapshotRepository = mock(SpacecraftSnapshotRepository::class.java)
        val projection = mock(PlanProjectionService::class.java)

        val service = PlanQueryService(plannedRepository, snapshotRepository, projection)
        val result = service.getDraftPlans(spacecraftId = null)

        assertEquals(1, result.size)
        assertEquals(PlannedPlanStatus.DRAFT, result.single().status)
        assertEquals(draft.planId, result.single().planId)
    }

    @Test
    fun `getDraftPlans filters by spacecraftId when provided`() {
        val draft = plannedPlan(time(2), time(5), PlannedPlanStatus.DRAFT)
        val plannedRepository = mock(PlannedPlanRepository::class.java)
        `when`(plannedRepository.findAllBySpacecraftIdOrderByStartTime(spacecraftId)).thenReturn(listOf(draft))
        val snapshotRepository = mock(SpacecraftSnapshotRepository::class.java)
        val projection = mock(PlanProjectionService::class.java)

        val service = PlanQueryService(plannedRepository, snapshotRepository, projection)
        val result = service.getDraftPlans(spacecraftId = spacecraftId)

        assertEquals(1, result.size)
        assertEquals(draft.planId, result.single().planId)
    }

    private fun plannedPlan(start: LocalDateTime, end: LocalDateTime, status: PlannedPlanStatus): PlannedPlanEntity =
        PlannedPlanEntity(
            planId = UUID.randomUUID(),
            spacecraftId = spacecraftId,
            startTime = start,
            endTime = end,
            kppId = "KPP-1",
            status = status
        )

    private fun calculated(start: LocalDateTime, end: LocalDateTime): CalculatedPlan =
        CalculatedPlan(spacecraftId = spacecraftId, startTime = start, endTime = end, kppId = "KPP-1")

    private fun time(hour: Int): LocalDateTime = LocalDateTime.now().plusHours(hour.toLong())
}
