package org.nstart.dep265.requestservice.service

import org.junit.jupiter.api.Test
import org.nstart.dep265.requestservice.PcpRequestServiceApplication
import org.nstart.dep265.requestservice.entity.EarthCellEntity
import org.nstart.dep265.requestservice.entity.RequestCellEntity
import org.nstart.dep265.requestservice.repository.EarthCellRepository
import org.nstart.dep265.requestservice.repository.RequestCellRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.transaction.annotation.Transactional
import java.util.UUID
import jakarta.persistence.EntityManager
import kotlin.test.assertEquals

@SpringBootTest(
    classes = [PcpRequestServiceApplication::class],
    webEnvironment = SpringBootTest.WebEnvironment.NONE,
    properties = [
        "spring.config.import=",
        "spring.cloud.config.enabled=false",
        "spring.flyway.enabled=false",
        "spring.jpa.hibernate.ddl-auto=create-drop",
        "spring.datasource.url=jdbc:h2:mem:cells-query;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false",
        "spring.datasource.driver-class-name=org.h2.Driver",
        "spring.datasource.username=sa",
        "spring.datasource.password=",
        "spring.jpa.database-platform=org.hibernate.dialect.H2Dialect",
        "spring.kafka.listener.auto-startup=false",
        "spring.kafka.consumer.group-id=request-service-test",
        "app.kafka.topics.route=request-service-test-route",
    ],
)
@Transactional
class CellsQueryServiceJpaTest @Autowired constructor(
    private val earthCellRepository: EarthCellRepository,
    private val requestCellRepository: RequestCellRepository,
    private val entityManager: EntityManager,
    private val service: CellsQueryService,
) {
    @Test
    fun `list cells without min importance returns all cells paged`() {
        saveCell(cellNum = 1, importance = 3.0)
        saveCell(cellNum = 2, importance = 2.0)
        saveCell(cellNum = 3, importance = 1.0)

        val response = service.listCells(
            minImportance = null,
            countLat = null,
            countLong = null,
            page = 1,
            size = 2,
            sort = null,
        )

        assertEquals(listOf(3L), response.items.map { cell -> cell.cellNum })
        assertEquals(1, response.page)
        assertEquals(2, response.size)
        assertEquals(3, response.totalItems)
        assertEquals(2, response.totalPages)
    }

    @Test
    fun `list cells filters by min importance in repository query`() {
        saveCell(cellNum = 1, importance = 3.0)
        saveCell(cellNum = 2, importance = 1.0)
        saveCell(cellNum = 3, importance = 2.0)

        val response = service.listCells(
            minImportance = 2.0,
            countLat = null,
            countLong = null,
            page = 0,
            size = 50,
            sort = null,
        )

        assertEquals(listOf(1L), response.items.map { cell -> cell.cellNum })
        assertEquals(1, response.totalItems)
    }

    @Test
    fun `list cells uses strict min importance filter and counts only matching cells`() {
        saveCell(cellNum = 1, importance = 0.0)
        saveCell(cellNum = 2, importance = 0.5)

        val response = service.listCells(
            minImportance = 0.0,
            countLat = null,
            countLong = null,
            page = 0,
            size = 50,
            sort = null,
        )

        assertEquals(listOf(2L), response.items.map { cell -> cell.cellNum })
        assertEquals(1, response.totalItems)
        assertEquals(1, response.totalPages)
    }

    @Test
    fun `list cells excludes cells equal to legacy min importance threshold`() {
        saveCell(cellNum = 1, importance = 0.001)
        saveCell(cellNum = 2, importance = 0.002)

        val response = service.listCells(
            minImportance = 0.001,
            countLat = null,
            countLong = null,
            page = 0,
            size = 50,
            sort = null,
        )

        assertEquals(listOf(2L), response.items.map { cell -> cell.cellNum })
        assertEquals(1, response.totalItems)
    }

    @Test
    fun `list cells without min importance includes zero importance cells`() {
        saveCell(cellNum = 1, importance = 0.0)
        saveCell(cellNum = 2, importance = 0.5)

        val response = service.listCells(
            minImportance = null,
            countLat = null,
            countLong = null,
            page = 0,
            size = 50,
            sort = null,
        )

        assertEquals(listOf(2L, 1L), response.items.map { cell -> cell.cellNum })
        assertEquals(2, response.totalItems)
    }

    @Test
    fun `list cells uses default importance desc and cell num asc sort`() {
        saveCell(cellNum = 3, importance = 5.0)
        saveCell(cellNum = 1, importance = 5.0)
        saveCell(cellNum = 2, importance = 7.0)

        val response = service.listCells(
            minImportance = null,
            countLat = null,
            countLong = null,
            page = 0,
            size = 50,
            sort = null,
        )

        assertEquals(listOf(2L, 1L, 3L), response.items.map { cell -> cell.cellNum })
    }

    @Test
    fun `list cells supports whitelisted sort field`() {
        saveCell(cellNum = 1, latitude = 55.0)
        saveCell(cellNum = 2, latitude = 53.0)

        val response = service.listCells(
            minImportance = null,
            countLat = null,
            countLong = null,
            page = 0,
            size = 50,
            sort = "latitude,asc",
        )

        assertEquals(listOf(2L, 1L), response.items.map { cell -> cell.cellNum })
    }

    @Test
    fun `list cells returns response fields matching openapi schema`() {
        saveCell(
            cellNum = 42,
            latitude = 55.0,
            longitude = 37.0,
            importance = 10.0,
            contour = "POLYGON ((36 55, 37 55, 37 56, 36 56, 36 55))",
        )

        val response = service.listCells(
            minImportance = null,
            countLat = null,
            countLong = null,
            page = 0,
            size = 50,
            sort = null,
        )

        val cell = response.items.single()
        assertEquals(42L, cell.cellNum)
        assertEquals(55.0, cell.latitude)
        assertEquals(37.0, cell.longitude)
        assertEquals(10.0, cell.importance)
        assertEquals("POLYGON ((36 55, 37 55, 37 56, 36 56, 36 55))", cell.contour)
    }

    @Test
    fun `list cells with requests returns all cells and request fragment fields`() {
        val requestId = UUID.fromString("00000000-0000-0000-0000-000000000201")
        val cellWithRequest = saveCell(cellNum = 10, importance = 5.0)
        saveCell(cellNum = 11, importance = 0.0)
        requestCellRepository.saveAndFlush(
            RequestCellEntity(
                requestId = requestId,
                coveragePercent = 33.0,
                importance = 7.0,
                contour = "POLYGON ((1 1, 2 1, 2 2, 1 2, 1 1))",
                cell = cellWithRequest,
            )
        )
        entityManager.clear()

        val response = service.listCellsPriorityMap(
            minImportance = null,
            countLat = null,
            countLong = null,
        )

        assertEquals(listOf(10L, 11L), response.items.map { cell -> cell.cellNum })
        assertEquals(emptyList(), response.items[1].requests)

        val fragment = response.items.first().requests.single()
        assertEquals(requestId, fragment.requestId)
        assertEquals("POLYGON ((1 1, 2 1, 2 2, 1 2, 1 1))", fragment.contour)
        assertEquals(33.0, fragment.coveragePercent)
        assertEquals(7.0, fragment.importance)
    }

    @Test
    fun `list cells with requests filters by earth cell importance`() {
        saveCell(cellNum = 10, importance = 5.0)
        saveCell(cellNum = 11, importance = 2.0)
        entityManager.clear()

        val response = service.listCellsPriorityMap(
            minImportance = 2.0,
            countLat = null,
            countLong = null,
        )

        assertEquals(listOf(10L), response.items.map { cell -> cell.cellNum })
    }

    @Test
    fun `list cells with requests uses strict min importance filter`() {
        saveCell(cellNum = 10, importance = 0.0)
        saveCell(cellNum = 11, importance = 1.0)
        entityManager.clear()

        val response = service.listCellsPriorityMap(
            minImportance = 0.0,
            countLat = null,
            countLong = null,
        )

        assertEquals(listOf(11L), response.items.map { cell -> cell.cellNum })
    }

    @Test
    fun `list cells with requests excludes cells equal to legacy min importance threshold`() {
        saveCell(cellNum = 10, importance = 0.001)
        saveCell(cellNum = 11, importance = 0.002)
        entityManager.clear()

        val response = service.listCellsPriorityMap(
            minImportance = 0.001,
            countLat = null,
            countLong = null,
        )

        assertEquals(listOf(11L), response.items.map { cell -> cell.cellNum })
    }

    private fun saveCell(
        cellNum: Long,
        latitude: Double = 55.0,
        longitude: Double = 37.0,
        importance: Double = 0.0,
        contour: String = "POLYGON ((36 55, 37 55, 37 56, 36 56, 36 55))",
    ): EarthCellEntity {
        return earthCellRepository.saveAndFlush(
            EarthCellEntity(
                cellNum = cellNum,
                latitude = latitude,
                longitude = longitude,
                importance = importance,
                contour = contour,
            )
        )
    }
}
