package space.nstart.pcp.pcp_mission_planing_service.service

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
import org.springframework.http.HttpStatus
import org.springframework.web.server.ResponseStatusException
import space.nstart.pcp.pcp_mission_planing_service.entity.DropModeEntity
import space.nstart.pcp.pcp_mission_planing_service.entity.DropModeSurveyEntity
import space.nstart.pcp.pcp_mission_planing_service.entity.ModeBookingEntity
import space.nstart.pcp.pcp_mission_planing_service.entity.ModeEntity
import space.nstart.pcp.pcp_mission_planing_service.entity.PlanEntity
import space.nstart.pcp.pcp_mission_planing_service.entity.SurveyModeEntity
import space.nstart.pcp.pcp_mission_planing_service.entity.SurveyModeSource
import space.nstart.pcp.pcp_mission_planing_service.repository.DropModeRepository
import space.nstart.pcp.pcp_mission_planing_service.repository.DropModeSurveyRepository
import space.nstart.pcp.pcp_mission_planing_service.repository.MissionRepository
import space.nstart.pcp.pcp_mission_planing_service.repository.ModeBookingRepository
import space.nstart.pcp.pcp_mission_planing_service.repository.ModeRepository
import space.nstart.pcp.pcp_mission_planing_service.repository.PlanRepository
import space.nstart.pcp.pcp_mission_planing_service.repository.SurveyModeRepository
import space.nstart.pcp.pcp_mission_planing_service.service.satellite_type.DefaultSatelliteMissionType
import space.nstart.pcp.pcp_mission_planing_service.service.satellite_type.EmissioSatelliteMissionType
import space.nstart.pcp.pcp_mission_planing_service.service.satellite_type.ReflixioSatelliteMissionType
import space.nstart.pcp.pcp_mission_planing_service.service.satellite_type.SatelliteMissionTypeFactory
import space.nstart.pcp.pcp_types_lib.dto.routes.SurveyModeStatus
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.DropRouteSaveRequestDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SurveyRouteBuildRequestDTO
import java.time.LocalDateTime
import java.util.UUID

class MissionEditModesTest {

    private val surveyStore = linkedMapOf<Long, SurveyModeEntity>()
    private val dropStore = linkedMapOf<Long, DropModeEntity>()
    private val dropSurveyStore = mutableListOf<DropModeSurveyEntity>()
    private val bookingStore = mutableListOf<ModeBookingEntity>()

    private var autoId = 10L

    private val missionRepository = mock(MissionRepository::class.java)

    private val modeRepository = mock(ModeRepository::class.java).also { repo ->
        `when`(repo.findById(anyLong())).thenAnswer { inv ->
            val id = inv.arguments[0] as Long
            java.util.Optional.ofNullable<ModeEntity>(surveyStore[id] ?: dropStore[id])
        }
    }

    private val surveyModeRepository = mock(SurveyModeRepository::class.java).also { repo ->
        doAnswer { inv ->
            val s = inv.arguments[0] as SurveyModeEntity
            surveyStore.remove(s.id)
            null
        }.`when`(repo).delete(anyNonNull())
    }

    private val dropModeRepository = mock(DropModeRepository::class.java).also { repo ->
        doAnswer { inv ->
            val d = inv.arguments[0] as DropModeEntity
            dropStore.remove(d.id)
            null
        }.`when`(repo).delete(anyNonNull())
        doAnswer { inv ->
            dropStore.remove(inv.arguments[0] as Long)
            null
        }.`when`(repo).deleteById(anyLong())
    }

    private val dropModeSurveyRepository = mock(DropModeSurveyRepository::class.java).also { repo ->
        `when`(repo.findAllBySurveyMode_Id(anyLong())).thenAnswer { inv ->
            val sid = inv.arguments[0] as Long
            dropSurveyStore.filter { it.surveyMode?.id == sid }
        }
        `when`(repo.findAllByDropMode_IdOrderBySurveyMode_TimeStartAsc(anyLong())).thenAnswer { inv ->
            val did = inv.arguments[0] as Long
            dropSurveyStore.filter { it.dropMode?.id == did }
        }
        doAnswer { inv ->
            @Suppress("UNCHECKED_CAST")
            val links = inv.arguments[0] as Iterable<DropModeSurveyEntity>
            dropSurveyStore.removeAll(links.toSet())
            null
        }.`when`(repo).deleteAll(anyNonNull())
    }

    private val modeBookingRepository = mock(ModeBookingRepository::class.java).also { repo ->
        `when`(repo.findAllByMode_IdIn(anyNonNull())).thenAnswer { inv ->
            @Suppress("UNCHECKED_CAST")
            val ids = inv.arguments[0] as Collection<Long>
            bookingStore.filter { it.mode?.id in ids }
        }
        doAnswer { inv ->
            @Suppress("UNCHECKED_CAST")
            val bs = inv.arguments[0] as Iterable<ModeBookingEntity>
            bookingStore.removeAll(bs.toSet())
            null
        }.`when`(repo).deleteAll(anyNonNull())
    }

    private val service = MissionPlaningService(
        missionRepository = missionRepository,
        modeRepository = modeRepository,
        planRepository = mock(PlanRepository::class.java),
        surveyModeRepository = surveyModeRepository,
        dropModeRepository = dropModeRepository,
        dropModeSurveyRepository = dropModeSurveyRepository,
        modeBookingRepository = modeBookingRepository,
        bookedSlotsEventPublisher = mock(BookedSlotsEventPublisher::class.java),
        ballisticsService = mock(BallisticsService::class.java),
        complexMissionService = mock(ComplexMissionService::class.java),
        satelliteCatalogClient = mock(space.nstart.pcp.pcp_mission_planing_service.service.catalog.SatelliteCatalogClient::class.java),
        satelliteMissionTypeFactory = SatelliteMissionTypeFactory(
            listOf(DefaultSatelliteMissionType(), EmissioSatelliteMissionType(), ReflixioSatelliteMissionType()),
            DefaultSatelliteMissionType()
        )
    )

    private val plan = PlanEntity(id = 1L, planNumber = 1)

    // --- DELETE mode ---

    @Test
    fun `deleting a survey removes its bookings, links and the orphaned drop`() {
        val survey = survey()
        val drop = drop()
        link(drop, survey)
        booking(survey)

        service.deleteMode(survey.id!!)

        assertFalse(surveyStore.containsKey(survey.id))
        assertTrue(bookingStore.isEmpty())
        assertFalse(dropStore.containsKey(drop.id), "сброс без единственной съёмки должен быть удалён")
        assertTrue(dropSurveyStore.isEmpty())
    }

    @Test
    fun `deleting a survey keeps a drop still referenced by another survey`() {
        val survey1 = survey()
        val survey2 = survey()
        val drop = drop()
        link(drop, survey1)
        link(drop, survey2)

        service.deleteMode(survey1.id!!)

        assertFalse(surveyStore.containsKey(survey1.id))
        assertTrue(surveyStore.containsKey(survey2.id))
        assertTrue(dropStore.containsKey(drop.id), "сброс с оставшейся съёмкой не удаляется")
        assertEquals(1, dropSurveyStore.size)
        assertEquals(survey2.id, dropSurveyStore.single().surveyMode?.id)
    }

    @Test
    fun `deleting a drop removes the drop and its links but keeps surveys`() {
        val survey = survey()
        val drop = drop()
        link(drop, survey)

        service.deleteMode(drop.id!!)

        assertFalse(dropStore.containsKey(drop.id))
        assertTrue(dropSurveyStore.isEmpty())
        assertTrue(surveyStore.containsKey(survey.id), "съёмка независима от сброса и сохраняется")
    }

    @Test
    fun `deleting an unknown mode yields 404`() {
        val ex = assertThrows<ResponseStatusException> { service.deleteMode(999L) }
        assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
    }

    // --- save routes больше не создают живую миссию ---

    @Test
    fun `saveSurveyRoute on a missing mission yields 404 instead of auto-creating`() {
        // missionRepository не застаблен → findById возвращает пустой Optional.
        val ex = assertThrows<ResponseStatusException> {
            service.saveSurveyRoute(UUID.randomUUID(), SurveyRouteBuildRequestDTO(satelliteId = 25544))
        }
        assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
    }

    @Test
    fun `saveDropRoute on a missing mission yields 404 instead of auto-creating`() {
        val ex = assertThrows<ResponseStatusException> {
            service.saveDropRoute(
                UUID.randomUUID(),
                DropRouteSaveRequestDTO(satelliteId = 25544, surveyModeIds = listOf(1L))
            )
        }
        assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
    }

    // --- validate (заглушка) ---

    @Test
    fun `validate of an existing mission returns an empty conflict list`() {
        val missionId = UUID.randomUUID()
        `when`(missionRepository.existsById(missionId)).thenReturn(true)

        val result = service.validateMission(missionId)

        assertEquals(missionId, result.missionId)
        assertTrue(result.conflicts.isEmpty(), "MVP-заглушка не считает конфликтов")
    }

    @Test
    fun `validate of a missing mission yields 404`() {
        val ex = assertThrows<ResponseStatusException> { service.validateMission(UUID.randomUUID()) }
        assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
    }

    // --- createBlankMission (план новой цепочки «без истории») ---

    @Test
    fun `createBlankMission persists a mission and its plan with the given window`() {
        val saved = arrayOfNulls<space.nstart.pcp.pcp_mission_planing_service.entity.MissionEntity>(1)
        `when`(missionRepository.save(anyNonNull())).thenAnswer { inv ->
            val m = inv.arguments[0] as space.nstart.pcp.pcp_mission_planing_service.entity.MissionEntity
            saved[0] = m
            m
        }
        val start = LocalDateTime.of(2026, 6, 10, 12, 0)
        val stop = LocalDateTime.of(2026, 6, 10, 12, 10)

        val response = service.createBlankMission(
            space.nstart.pcp.pcp_mission_planing_service.controller.MissionBlankRequest(
                satelliteId = 25544, missionStart = start, missionStop = stop
            )
        )

        assertEquals(saved[0]!!.missionId, response.newMissionId)
        assertEquals(25544L, saved[0]!!.satelliteId)
        assertEquals(start, saved[0]!!.missionStart)
        assertEquals(stop, saved[0]!!.missionStop)
        assertEquals("NEW", saved[0]!!.status)
    }

    @Test
    fun `createBlankMission with stop not after start yields 400`() {
        val t = LocalDateTime.of(2026, 6, 10, 12, 0)
        val ex = assertThrows<ResponseStatusException> {
            service.createBlankMission(
                space.nstart.pcp.pcp_mission_planing_service.controller.MissionBlankRequest(
                    satelliteId = 25544, missionStart = t, missionStop = t
                )
            )
        }
        assertEquals(HttpStatus.BAD_REQUEST, ex.statusCode)
    }

    // --- helpers ---

    private fun survey(): SurveyModeEntity {
        val id = ++autoId
        val s = SurveyModeEntity(
            id = id, plan = plan, timeStart = LocalDateTime.of(2026, 6, 10, 12, 0),
            revolution = id, lat = 55.0, long = 37.0, duration = 60.0, roll = 0.0,
            status = SurveyModeStatus.PLANNED, source = SurveyModeSource.MANUAL
        )
        surveyStore[id] = s
        return s
    }

    private fun drop(): DropModeEntity {
        val id = ++autoId
        val d = DropModeEntity(
            id = id, plan = plan, timeStart = LocalDateTime.of(2026, 6, 10, 13, 0),
            revolution = id, station = "102", duration = 60.0
        )
        dropStore[id] = d
        return d
    }

    private fun link(drop: DropModeEntity, survey: SurveyModeEntity) {
        dropSurveyStore.add(DropModeSurveyEntity(dropMode = drop, surveyMode = survey))
    }

    private fun booking(survey: SurveyModeEntity) {
        bookingStore.add(ModeBookingEntity(id = ++autoId, mode = survey, slotId = autoId))
    }

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