package org.nstart.dep265.requestservice.service

import org.junit.jupiter.api.Test
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyList
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoInteractions
import org.mockito.Mockito.`when`
import org.nstart.dep265.requestservice.entity.EarthCellEntity
import org.nstart.dep265.requestservice.entity.EarthGridSettingsEntity
import org.nstart.dep265.requestservice.entity.RequestCellEntity
import org.nstart.dep265.requestservice.repository.EarthCellRepository
import org.nstart.dep265.requestservice.repository.EarthGridSettingsRepository
import java.util.Optional
import java.util.UUID
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class EarthGridCatalogServiceTest {

    private val earthCellRepository = mock(EarthCellRepository::class.java)
    private val earthGridSettingsRepository = mock(EarthGridSettingsRepository::class.java)
    private val geometryService = GeometryService()
    private val compatGeometryService = CompatGeometryService(geometryService)

    @Test
    fun `init stores requested calculation step`() {
        val currentSettings = EarthGridSettingsEntity(calculationStep = 2.0)
        `when`(earthGridSettingsRepository.findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID))
            .thenReturn(Optional.of(currentSettings))
        val service = service(gridEnabled = false)

        service.init(3.5)

        val settingsCaptor = ArgumentCaptor.forClass(EarthGridSettingsEntity::class.java)
        verify(earthGridSettingsRepository).save(settingsCaptor.capture())
        verifyNoInteractions(earthCellRepository)
        assertEquals(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID, settingsCaptor.value.settingsId)
        assertEquals(3.5, settingsCaptor.value.calculationStep)
    }

    @Test
    fun `init creates settings row when it is missing`() {
        `when`(earthGridSettingsRepository.findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID))
            .thenReturn(Optional.empty())
        val service = service(gridEnabled = false)

        service.init(1.0)

        val settingsCaptor = ArgumentCaptor.forClass(EarthGridSettingsEntity::class.java)
        verify(earthGridSettingsRepository).save(settingsCaptor.capture())
        assertEquals(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID, settingsCaptor.value.settingsId)
        assertEquals(1.0, settingsCaptor.value.calculationStep)
    }

    @Test
    fun `init creates earth cells with zero non-null importance`() {
        `when`(earthGridSettingsRepository.findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID))
            .thenReturn(Optional.of(EarthGridSettingsEntity(calculationStep = 2.0)))
        val savedCells = mutableListOf<EarthCellEntity>()
        org.mockito.Mockito.doAnswer { invocation ->
            val cells = invocation.getArgument<List<EarthCellEntity>>(0)
            savedCells += cells
            cells
        }.`when`(earthCellRepository).saveAll(anyList())
        val service = service(gridEnabled = true)

        service.init(180.0)

        assertTrue(savedCells.isNotEmpty())
        assertTrue(savedCells.all { earthCell -> earthCell.importance == 0.0 })
    }

    @Test
    fun `by importance returns source cells when aggregation params are absent`() {
        `when`(earthCellRepository.findAllByImportanceGreaterThanOrderByImportanceDesc(0.0))
            .thenReturn(listOf(cell(id = 1L, num = 10L, latitude = -88.0, longitude = 0.0, importance = 2.0)))
        val service = service(gridEnabled = false)

        val cells = service.byImportance(0.0)

        assertEquals(1, cells.size)
        assertEquals(10L, cells.first().num)
        assertEquals(2.0, cells.first().importance)
    }

    @Test
    fun `by importance aggregates neighbor cells using stored calculation step`() {
        `when`(earthGridSettingsRepository.findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID))
            .thenReturn(Optional.of(EarthGridSettingsEntity(calculationStep = 2.0)))
        `when`(earthCellRepository.findAllByImportanceGreaterThanOrderByImportanceDesc(0.0))
            .thenReturn(
                listOf(
                    cell(id = 1L, num = 10L, latitude = -88.0, longitude = 0.0, importance = 1.0),
                    cell(id = 2L, num = 11L, latitude = -88.0, longitude = 2.0, importance = 2.0),
                    cell(id = 3L, num = 12L, latitude = -86.0, longitude = 0.0, importance = 3.0),
                    cell(id = 4L, num = 13L, latitude = -86.0, longitude = 2.0, importance = 4.0),
                )
            )
        val service = service(gridEnabled = false)

        val cells = service.byImportance(0.0, countLat = 2, countLong = 2)

        assertEquals(1, cells.size)
        assertEquals(10.0, cells.first().importance)
        assertEquals(-88.0, cells.first().latitude)
        assertEquals(0.0, cells.first().longitude)
        assertEquals(
            "POLYGON ((-2.0 -90.0, 2.0 -90.0, 2.0 -86.0, -2.0 -86.0, -2.0 -90.0))",
            cells.first().contour,
        )
    }

    @Test
    fun `with requests aggregates cells and keeps request fragments`() {
        `when`(earthGridSettingsRepository.findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID))
            .thenReturn(Optional.of(EarthGridSettingsEntity(calculationStep = 2.0)))
        `when`(earthCellRepository.findCellsWithRequestProjections(null))
            .thenReturn(
                listOf(
                    cellWithRequest(id = 1L, num = 10L, latitude = -88.0, longitude = 0.0, importance = 1.0),
                    cellWithRequest(id = 2L, num = 11L, latitude = -88.0, longitude = 2.0, importance = 2.0),
                )
            )
        val service = service(gridEnabled = false)

        val cells = service.allWithRequests(countLat = 1, countLong = 2)

        assertEquals(1, cells.size)
        assertEquals(3.0, cells.first().importance)
        assertEquals(2, cells.first().requests.count())
    }

    private fun service(gridEnabled: Boolean) =
        EarthGridCatalogService(
            earthCellRepository = earthCellRepository,
            earthGridSettingsRepository = earthGridSettingsRepository,
            compatGeometryService = compatGeometryService,
            gridEnabled = gridEnabled,
        )

    private fun cell(
        id: Long,
        num: Long,
        latitude: Double,
        longitude: Double,
        importance: Double,
    ) = EarthCellEntity(
        cellId = id,
        cellNum = num,
        latitude = latitude,
        longitude = longitude,
        importance = importance,
        contour = "",
    )

    private fun cellWithRequest(
        id: Long,
        num: Long,
        latitude: Double,
        longitude: Double,
        importance: Double,
    ): EarthCellEntity {
        val earthCell = cell(id, num, latitude, longitude, importance)
        earthCell.requestProjections = mutableListOf(
            RequestCellEntity(
                id = id,
                requestId = UUID.fromString("00000000-0000-0000-0000-${id.toString().padStart(12, '0')}"),
                contour = "POLYGON EMPTY",
                importance = importance,
                cell = earthCell,
            )
        )
        return earthCell
    }
}
