package org.nstart.dep265.requestservice.service

import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers.anyCollection
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoInteractions
import org.mockito.Mockito.inOrder
import org.nstart.dep265.requestservice.entity.EarthGridSettingsEntity
import org.nstart.dep265.requestservice.entity.RequestEntity
import org.nstart.dep265.requestservice.entity.RequestStatus
import org.nstart.dep265.requestservice.entity.RequestSurveyType
import org.nstart.dep265.requestservice.repository.EarthCellRepository
import org.nstart.dep265.requestservice.repository.EarthGridSettingsRepository
import org.nstart.dep265.requestservice.repository.RequestCellRepository
import org.nstart.dep265.requestservice.repository.RequestRepository
import java.time.LocalDateTime
import java.util.Optional
import java.util.UUID
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class GridRebuildServiceTest {
    private val earthGridSettingsRepository = mock(EarthGridSettingsRepository::class.java).also { repository ->
        doAnswer { invocation -> invocation.getArgument<EarthGridSettingsEntity>(0) }
            .`when`(repository)
            .save(org.mockito.ArgumentMatchers.any(EarthGridSettingsEntity::class.java))
    }
    private val gridSettingsService = GridSettingsService(earthGridSettingsRepository)
    private val earthGridCatalogService = mock(EarthGridCatalogService::class.java)
    private val requestCellRepository = mock(RequestCellRepository::class.java)
    private val requestRepository = mock(RequestRepository::class.java)
    private val requestGridProjectionService = mock(RequestGridProjectionService::class.java)
    private val earthCellRepository = mock(EarthCellRepository::class.java)
    private val gridRebuildLockService = mock(GridRebuildLockService::class.java)

    @Test
    fun `rebuild grid rebuilds catalog and projections only for active requests`() {
        val acceptedRequest = request(status = RequestStatus.ACCEPTED)
        val activeRequest = request(status = RequestStatus.ACTIVE)
        val completedRequest = request(status = RequestStatus.COMPLETED)
        val expiredRequest = request(status = RequestStatus.EXPIRED)
        val deletedRequest = request(status = RequestStatus.DELETED)

        doReturn(true).`when`(gridRebuildLockService).tryAcquireTransactionLock()
        doReturn(Optional.of(EarthGridSettingsEntity(calculationStep = 180.0)))
            .`when`(earthGridSettingsRepository)
            .findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID)
        doReturn(listOf(acceptedRequest, activeRequest))
            .`when`(requestRepository)
            .findByStatusIn(anyCollection())
        doReturn(2L).`when`(earthCellRepository).count()

        val response = service().rebuildGrid()

        assertEquals(2L, response.cellsCount)
        assertEquals(2L, response.rebuiltRequestProjectionsCount)

        val order = inOrder(requestCellRepository, earthGridCatalogService, requestGridProjectionService)
        order.verify(requestCellRepository).deleteAllInBatch()
        order.verify(earthGridCatalogService).init(180.0)
        order.verify(requestGridProjectionService).rebuildProjection(acceptedRequest)
        order.verify(requestGridProjectionService).rebuildProjection(activeRequest)

        verify(requestRepository).findByStatusIn(setOf(RequestStatus.ACCEPTED, RequestStatus.ACTIVE))
        verify(requestGridProjectionService, never()).rebuildProjection(completedRequest)
        verify(requestGridProjectionService, never()).rebuildProjection(expiredRequest)
        verify(requestGridProjectionService, never()).rebuildProjection(deletedRequest)
    }

    @Test
    fun `rebuild grid uses default settings when settings row is missing`() {
        doReturn(true).`when`(gridRebuildLockService).tryAcquireTransactionLock()
        doReturn(Optional.empty<EarthGridSettingsEntity>())
            .`when`(earthGridSettingsRepository)
            .findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID)
        doReturn(emptyList<RequestEntity>())
            .`when`(requestRepository)
            .findByStatusIn(anyCollection())
        doReturn(0L).`when`(earthCellRepository).count()

        val response = service().rebuildGrid()

        assertEquals(0L, response.rebuiltRequestProjectionsCount)
        verify(earthGridCatalogService).init(EarthGridSettingsEntity.DEFAULT_CALCULATION_STEP)
    }

    @Test
    fun `rebuild grid fails with conflict before side effects when lock is busy`() {
        doReturn(false).`when`(gridRebuildLockService).tryAcquireTransactionLock()

        assertFailsWith<GridRebuildAlreadyRunningException> {
            service().rebuildGrid()
        }

        verifyNoInteractions(
            earthGridSettingsRepository,
            earthGridCatalogService,
            requestCellRepository,
            requestRepository,
            requestGridProjectionService,
            earthCellRepository,
        )
    }

    @Test
    fun `rebuild grid does not mutate request domain fields`() {
        val activeRequest = request(status = RequestStatus.ACTIVE)
        val remainingGeometry = activeRequest.remainingGeometry
        val remainingArea = activeRequest.remainingArea

        doReturn(true).`when`(gridRebuildLockService).tryAcquireTransactionLock()
        doReturn(Optional.of(EarthGridSettingsEntity(calculationStep = 180.0)))
            .`when`(earthGridSettingsRepository)
            .findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID)
        doReturn(listOf(activeRequest))
            .`when`(requestRepository)
            .findByStatusIn(anyCollection())
        doReturn(2L).`when`(earthCellRepository).count()

        service().rebuildGrid()

        assertEquals(remainingGeometry, activeRequest.remainingGeometry)
        assertEquals(remainingArea, activeRequest.remainingArea)
        assertEquals(RequestStatus.ACTIVE, activeRequest.status)
    }

    private fun service() =
        GridRebuildService(
            gridSettingsService = gridSettingsService,
            earthGridCatalogService = earthGridCatalogService,
            requestCellRepository = requestCellRepository,
            requestRepository = requestRepository,
            requestGridProjectionService = requestGridProjectionService,
            earthCellRepository = earthCellRepository,
            gridRebuildLockService = gridRebuildLockService,
        )

    private fun request(status: RequestStatus): RequestEntity {
        val geometry = "POLYGON ((0 1, 1 1, 1 0, 0 0, 0 1))"
        val now = LocalDateTime.parse("2026-05-20T10:00:00")
        return RequestEntity(
            id = UUID.randomUUID(),
            name = "Тест1",
            geometry = geometry,
            remainingGeometry = geometry,
            geometryArea = 1.0,
            remainingArea = 0.5,
            importance = 7.0,
            beginDateTime = now,
            endDateTime = now.plusDays(1),
            surveyType = RequestSurveyType.OPTICS,
            status = status,
            createdAt = now,
            updatedAt = now,
            deletedAt = if (status == RequestStatus.DELETED) now else null,
        )
    }
}
