package org.nstart.dep265.requestservice.controller

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
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 org.nstart.dep265.requestservice.service.CellsQueryService
import org.nstart.dep265.requestservice.service.CompatGeometryService
import org.nstart.dep265.requestservice.service.EarthGridCatalogService
import org.nstart.dep265.requestservice.service.GeometryService
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
import org.springframework.http.MediaType
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import java.util.Optional
import java.util.UUID

class CellsControllerTest {
    private val requestId = UUID.fromString("00000000-0000-0000-0000-000000000301")
    private val cellWithRequest = EarthCellEntity(
        cellNum = 42,
        latitude = 55.0,
        longitude = 37.0,
        importance = 10.0,
        contour = "POLYGON ((36 55, 37 55, 37 56, 36 56, 36 55))",
    ).also { earthCell ->
        earthCell.requestProjections = mutableListOf(
            RequestCellEntity(
                requestId = requestId,
                contour = "POLYGON ((36.5 55.5, 37 55.5, 37 56, 36.5 56, 36.5 55.5))",
                coveragePercent = 25.0,
                importance = 7.0,
                cell = earthCell,
            )
        )
    }

    private val earthCellRepository = mock(EarthCellRepository::class.java).also { repository ->
        doReturn(
            PageImpl(
                listOf(cellWithRequest),
                PageRequest.of(0, 50),
                1,
            )
        )
            .`when`(repository)
            .findCells(isNull(), pageableMatcher())
        doReturn(
            listOf(
                cellWithRequest,
                EarthCellEntity(
                    cellNum = 43,
                    latitude = 54.0,
                    longitude = 38.0,
                    importance = 0.0,
                    contour = "POLYGON ((37 54, 38 54, 38 55, 37 55, 37 54))",
                )
            )
        )
            .`when`(repository)
            .findCellsWithRequestProjections(isNull())
    }

    private val earthGridSettingsRepository = mock(EarthGridSettingsRepository::class.java).also { repository ->
        doReturn(Optional.of(EarthGridSettingsEntity(calculationStep = 2.0)))
            .`when`(repository)
            .findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID)
    }

    private val earthGridCatalogService = EarthGridCatalogService(
        earthCellRepository = earthCellRepository,
        earthGridSettingsRepository = earthGridSettingsRepository,
        compatGeometryService = CompatGeometryService(GeometryService()),
        gridEnabled = false,
    )

    private val mockMvc = MockMvcBuilders
        .standaloneSetup(CellsController(CellsQueryService(earthCellRepository, earthGridCatalogService)))
        .setControllerAdvice(CellsApiExceptionHandler())
        .setMessageConverters(MappingJackson2HttpMessageConverter(jacksonObjectMapper()))
        .build()

    @Test
    fun `get v1 cells returns pageable list response`() {
        mockMvc.perform(get("/v1/cells").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk)
            .andExpect(jsonPath("$.items").isArray)
            .andExpect(jsonPath("$.items[0].cellNum").value(42))
            .andExpect(jsonPath("$.items[0].latitude").value(55.0))
            .andExpect(jsonPath("$.items[0].longitude").value(37.0))
            .andExpect(jsonPath("$.items[0].importance").value(10.0))
            .andExpect(jsonPath("$.items[0].contour").value("POLYGON ((36 55, 37 55, 37 56, 36 56, 36 55))"))
            .andExpect(jsonPath("$.page").value(0))
            .andExpect(jsonPath("$.size").value(50))
            .andExpect(jsonPath("$.totalItems").value(1))
            .andExpect(jsonPath("$.totalPages").value(1))
    }

    @Test
    fun `get v1 cells rejects unsupported sort field`() {
        mockMvc.perform(
            get("/v1/cells")
                .queryParam("sort", "geometry,asc")
                .accept(MediaType.APPLICATION_JSON),
        )
            .andExpect(status().isBadRequest)
            .andExpect(jsonPath("$.code").value("VALIDATION_ERROR"))
            .andExpect(jsonPath("$.message").value("Unsupported sort field: geometry"))
    }

    @Test
    fun `get v1 cells rejects countLat before aggregation implementation`() {
        mockMvc.perform(
            get("/v1/cells")
                .queryParam("countLat", "2")
                .accept(MediaType.APPLICATION_JSON),
        )
            .andExpect(status().isBadRequest)
            .andExpect(jsonPath("$.code").value("VALIDATION_ERROR"))
            .andExpect(jsonPath("$.message").value("Cell aggregation by countLat/countLong is not implemented yet"))
    }

    @Test
    fun `get v1 cells priority map returns request fragments`() {
        mockMvc.perform(get("/v1/cells/priority-map").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk)
            .andExpect(jsonPath("$.items").isArray)
            .andExpect(jsonPath("$.items[0].cellNum").value(42))
            .andExpect(jsonPath("$.items[0].latitude").value(55.0))
            .andExpect(jsonPath("$.items[0].longitude").value(37.0))
            .andExpect(jsonPath("$.items[0].importance").value(10.0))
            .andExpect(jsonPath("$.items[0].contour").value("POLYGON ((36 55, 37 55, 37 56, 36 56, 36 55))"))
            .andExpect(jsonPath("$.items[0].requests[0].requestId").value(requestId.toString()))
            .andExpect(jsonPath("$.items[0].requests[0].contour").value("POLYGON ((36.5 55.5, 37 55.5, 37 56, 36.5 56, 36.5 55.5))"))
            .andExpect(jsonPath("$.items[0].requests[0].coveragePercent").value(25.0))
            .andExpect(jsonPath("$.items[0].requests[0].importance").value(7.0))
            .andExpect(jsonPath("$.items[1].requests").isEmpty)
    }

    @Test
    fun `get v1 cells priority map rejects partial aggregation parameters`() {
        mockMvc.perform(
            get("/v1/cells/priority-map")
                .queryParam("countLat", "2")
                .accept(MediaType.APPLICATION_JSON),
        )
            .andExpect(status().isBadRequest)
            .andExpect(jsonPath("$.code").value("VALIDATION_ERROR"))
            .andExpect(jsonPath("$.message").value("countLat and countLong must be provided together"))
    }

    @Test
    fun `get v1 cells priority map rejects invalid numeric parameters`() {
        mockMvc.perform(
            get("/v1/cells/priority-map")
                .queryParam("minImportance", "-1")
                .accept(MediaType.APPLICATION_JSON),
        )
            .andExpect(status().isBadRequest)
            .andExpect(jsonPath("$.code").value("VALIDATION_ERROR"))
            .andExpect(jsonPath("$.message").value("minImportance must be greater than or equal to 0"))

        mockMvc.perform(
            get("/v1/cells/priority-map")
                .queryParam("countLat", "0")
                .queryParam("countLong", "1")
                .accept(MediaType.APPLICATION_JSON),
        )
            .andExpect(status().isBadRequest)
            .andExpect(jsonPath("$.code").value("VALIDATION_ERROR"))
            .andExpect(jsonPath("$.message").value("countLat must be greater than 0"))
    }

    @Test
    fun `get v1 cells priority map accepts complete aggregation parameters`() {
        mockMvc.perform(
            get("/v1/cells/priority-map")
                .queryParam("countLat", "1")
                .queryParam("countLong", "1")
                .accept(MediaType.APPLICATION_JSON),
        )
            .andExpect(status().isOk)
            .andExpect(jsonPath("$.items").isArray)
    }

    @Test
    fun `legacy earth grid with requests endpoint is not restored`() {
        mockMvc.perform(get("/api/earth-grid/with-requests").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isNotFound)
    }

    private fun pageableMatcher(): Pageable {
        any(Pageable::class.java)
        return PageRequest.of(0, 50)
    }
}
