package org.nstart.dep265.requestservice.service

import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock
import org.nstart.dep265.requestservice.entity.EarthCellEntity
import org.nstart.dep265.requestservice.entity.RequestCellEntity
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.RequestCellRepository
import org.nstart.dep265.requestservice.repository.RequestRepository
import java.time.LocalDateTime
import java.util.UUID
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class RequestGridProjectionServiceTest {

    private val geometryService = GeometryService()

    @Test
    fun `binds cells correctly for request in minus180 plus180 format crossing zero meridian`() {
        val savedFragments = rebuildProjection(
            geometry = "POLYGON ((-1 1, 1 1, 1 -1, -1 -1, -1 1))",
        )

        assertEquals(setOf(100L, 101L), savedFragments.map { fragment -> fragment.cell?.cellNum }.toSet())
        assertFragmentInCellBand(savedFragments, 100L, 359.0, 360.0)
        assertFragmentInCellBand(savedFragments, 101L, 0.0, 1.0)
    }

    @Test
    fun `binds cells correctly for request in zero360 format crossing zero meridian`() {
        val savedFragments = rebuildProjection(
            geometry = "POLYGON ((359 1, 1 1, 1 -1, 359 -1, 359 1))",
        )

        assertEquals(setOf(100L, 101L), savedFragments.map { fragment -> fragment.cell?.cellNum }.toSet())
        assertFragmentInCellBand(savedFragments, 100L, 359.0, 360.0)
        assertFragmentInCellBand(savedFragments, 101L, 0.0, 1.0)
    }

    @Test
    fun `request cells inherit request importance and make earth cell importance positive`() {
        val savedFragments = rebuildProjection(
            geometry = "POLYGON ((0 1, 1 1, 1 -1, 0 -1, 0 1))",
            importance = 7.5,
        )

        assertTrue(savedFragments.isNotEmpty())
        assertTrue(savedFragments.all { fragment -> fragment.importance == 7.5 })

        val earthCellImportance = savedFragments.sumOf { fragment ->
            fragment.importance * ((fragment.coveragePercent ?: 0.0) / 100.0)
        }
        assertTrue(earthCellImportance > 0.0)
    }

    private fun rebuildProjection(
        geometry: String,
        importance: Double = 10.0,
    ): List<RequestCellEntity> {
        val requestRepository = mock(RequestRepository::class.java)
        val earthCellRepository = mock(EarthCellRepository::class.java)
        val requestCellRepository = mock(RequestCellRepository::class.java)
        val savedFragments = mutableListOf<RequestCellEntity>()

        val service = RequestGridProjectionService(
            requestRepository = requestRepository,
            earthCellRepository = earthCellRepository,
            requestCellRepository = requestCellRepository,
            geometryService = geometryService,
            gridEnabled = true,
        )

        val cells = listOf(
            EarthCellEntity(
                cellId = 1L,
                cellNum = 100L,
                contour = "POLYGON ((359 1, 360 1, 360 -1, 359 -1, 359 1))",
            ),
            EarthCellEntity(
                cellId = 2L,
                cellNum = 101L,
                contour = "POLYGON ((0 1, 1 1, 1 -1, 0 -1, 0 1))",
            ),
        )

        doAnswer { invocation ->
            val polygonWkt = invocation.getArgument<String>(0)
            val queryGeometry = geometryService.parsePolygonalGeometry(polygonWkt)
            cells.filter { cell ->
                val cellGeometry = geometryService.parsePolygonalGeometry(cell.contour)
                queryGeometry.intersects(cellGeometry)
            }
        }.`when`(earthCellRepository).findIntersectingByContour(anyString())
        doAnswer { invocation ->
            val fragments = invocation.getArgument<List<RequestCellEntity>>(0)
            savedFragments += fragments
            fragments
        }.`when`(requestCellRepository).saveAll(org.mockito.ArgumentMatchers.anyList())

        service.rebuildProjection(
            RequestEntity(
                id = UUID.randomUUID(),
                name = "Тест1",
                geometry = geometry,
                remainingGeometry = geometry,
                geometryArea = 1.0,
                remainingArea = 1.0,
                importance = importance,
                beginDateTime = LocalDateTime.now(),
                endDateTime = LocalDateTime.now().plusDays(1),
                highPriorityTransmit = false,
                surveyType = RequestSurveyType.OPTICS,
                status = RequestStatus.ACTIVE,
                matchCount = 0,
                createdAt = LocalDateTime.now(),
                updatedAt = LocalDateTime.now(),
            )
        )

        return savedFragments
    }

    private fun assertFragmentInCellBand(
        savedFragments: List<RequestCellEntity>,
        cellNum: Long,
        expectedMinX: Double,
        expectedMaxX: Double,
    ) {
        val fragment = savedFragments.first { requestCell -> requestCell.cell?.cellNum == cellNum }
        val geometry = geometryService.parsePolygonalGeometry(fragment.contour)

        assertEquals(expectedMinX, geometry.envelopeInternal.minX)
        assertEquals(expectedMaxX, geometry.envelopeInternal.maxX)
        assertTrue(geometry.area > 0.0)
    }
}
