package space.nstart.pcp_tgu_service.service

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.mockito.ArgumentMatchers
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.springframework.http.HttpStatus
import org.springframework.web.server.ResponseStatusException
import space.nstart.pcp_tgu_service.entity.PlannedPlanEntity
import space.nstart.pcp_tgu_service.entity.PlannedPlanStatus
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
import java.time.LocalDateTime
import java.util.Optional
import java.util.UUID

class DismissPlanUseCaseTest {

    private val store = linkedMapOf<UUID, PlannedPlanEntity>()
    private val repo = mock(PlannedPlanRepository::class.java).also { repo ->
        `when`(repo.findById(anyUuid())).thenAnswer { inv -> Optional.ofNullable(store[inv.arguments[0]]) }
        doAnswer { inv -> (inv.arguments[0] as PlannedPlanEntity).also { store[it.planId] = it } }
            .`when`(repo).save(anyNonNull())
    }
    private val useCase = DismissPlanUseCase(repo)

    @Test
    fun `dismiss stamps dismissedAt on a REJECTED plan without changing status`() {
        val plan = plan(PlannedPlanStatus.REJECTED)
        store[plan.planId] = plan

        useCase.dismiss(plan.planId)

        assertNotNull(store[plan.planId]?.dismissedAt)
        assertEquals(PlannedPlanStatus.REJECTED, store[plan.planId]?.status)
    }

    @Test
    fun `dismiss is allowed for every terminal-negative status`() {
        for (status in listOf(
            PlannedPlanStatus.REJECTED,
            PlannedPlanStatus.START_AMBIGUOUS,
            PlannedPlanStatus.LAYIN_FAILED,
            PlannedPlanStatus.NOT_ACCEPTED,
            PlannedPlanStatus.SUPERSEDED
        )) {
            val plan = plan(status)
            store[plan.planId] = plan
            useCase.dismiss(plan.planId)
            assertNotNull(store[plan.planId]?.dismissedAt, "expected $status to be dismissible")
        }
    }

    @Test
    fun `dismiss refuses a live or successful plan with 422`() {
        for (status in listOf(
            PlannedPlanStatus.DRAFT,
            PlannedPlanStatus.ISSUING,
            PlannedPlanStatus.WAITING_DECISION,
            PlannedPlanStatus.AWAITING_LAYIN,
            PlannedPlanStatus.LAID_IN
        )) {
            val plan = plan(status)
            store[plan.planId] = plan
            val ex = assertThrows<ResponseStatusException> { useCase.dismiss(plan.planId) }
            assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, ex.statusCode, "expected $status to be refused")
            assertNull(store[plan.planId]?.dismissedAt)
        }
    }

    @Test
    fun `dismiss of an unknown plan yields 404`() {
        val ex = assertThrows<ResponseStatusException> { useCase.dismiss(UUID.randomUUID()) }
        assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
    }

    @Test
    fun `dismiss is idempotent — second call does not re-save`() {
        val plan = plan(PlannedPlanStatus.REJECTED)
        store[plan.planId] = plan
        useCase.dismiss(plan.planId)
        val firstStamp = store[plan.planId]?.dismissedAt

        useCase.dismiss(plan.planId)

        // повторный вызов не трогает запись (save вызван ровно один раз)
        verify(repo, org.mockito.Mockito.times(1)).save(anyNonNull())
        assertEquals(firstStamp, store[plan.planId]?.dismissedAt)
    }

    @Test
    fun `restore clears dismissedAt`() {
        val plan = plan(PlannedPlanStatus.REJECTED).also { it.dismissedAt = LocalDateTime.now() }
        store[plan.planId] = plan

        useCase.restore(plan.planId)

        assertNull(store[plan.planId]?.dismissedAt)
        assertEquals(PlannedPlanStatus.REJECTED, store[plan.planId]?.status)
    }

    @Test
    fun `restore of an unknown plan yields 404`() {
        val ex = assertThrows<ResponseStatusException> { useCase.restore(UUID.randomUUID()) }
        assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
    }

    private fun plan(status: PlannedPlanStatus): PlannedPlanEntity =
        PlannedPlanEntity(
            planId = UUID.randomUUID(),
            spacecraftId = "25544",
            startTime = LocalDateTime.now().plusHours(3),
            endTime = LocalDateTime.now().plusHours(4),
            kppId = "KPP-1",
            status = status
        )

    @Suppress("UNCHECKED_CAST")
    private fun <T> anyNonNull(): T { ArgumentMatchers.any<T>(); return null as T }
    private fun anyUuid(): UUID { ArgumentMatchers.any<UUID>(); return UUID.randomUUID() }
}
