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.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.doThrow
import org.mockito.Mockito.mock
import org.nstart.dep265.requestservice.dto.GridRebuildResponseDto
import org.nstart.dep265.requestservice.entity.EarthGridSettingsEntity
import org.nstart.dep265.requestservice.repository.EarthGridSettingsRepository
import org.nstart.dep265.requestservice.service.GridRebuildAlreadyRunningException
import org.nstart.dep265.requestservice.service.GridRebuildService
import org.nstart.dep265.requestservice.service.GridSettingsService
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.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
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.time.LocalDateTime
import java.util.Optional

class GridManagementControllerTest {
    private val earthGridSettingsRepository = mock(EarthGridSettingsRepository::class.java).also { repository ->
        doAnswer { invocation -> invocation.getArgument<EarthGridSettingsEntity>(0) }
            .`when`(repository)
            .save(any(EarthGridSettingsEntity::class.java))
    }
    private val gridRebuildService = mock(GridRebuildService::class.java)

    private val mockMvc = MockMvcBuilders
        .standaloneSetup(GridManagementController(GridSettingsService(earthGridSettingsRepository), gridRebuildService))
        .setControllerAdvice(GridManagementApiExceptionHandler())
        .setMessageConverters(MappingJackson2HttpMessageConverter(jacksonObjectMapper().findAndRegisterModules()))
        .build()

    @Test
    fun `get v1 grid settings returns current settings`() {
        doReturn(Optional.of(EarthGridSettingsEntity(calculationStep = 4.0)))
            .`when`(earthGridSettingsRepository)
            .findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID)

        mockMvc.perform(get("/v1/grid/settings").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk)
            .andExpect(jsonPath("$.settingsId").value(1))
            .andExpect(jsonPath("$.calculationStep").value(4.0))
    }

    @Test
    fun `get v1 grid settings creates and returns default settings when missing`() {
        doReturn(Optional.empty<EarthGridSettingsEntity>())
            .`when`(earthGridSettingsRepository)
            .findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID)

        mockMvc.perform(get("/v1/grid/settings").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk)
            .andExpect(jsonPath("$.settingsId").value(1))
            .andExpect(jsonPath("$.calculationStep").value(2.0))
    }

    @Test
    fun `put v1 grid settings updates settings`() {
        doReturn(Optional.of(EarthGridSettingsEntity(calculationStep = 2.0)))
            .`when`(earthGridSettingsRepository)
            .findById(EarthGridSettingsEntity.DEFAULT_SETTINGS_ID)

        mockMvc.perform(
            put("/v1/grid/settings")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .content("""{"calculationStep": 3.5}"""),
        )
            .andExpect(status().isOk)
            .andExpect(jsonPath("$.settingsId").value(1))
            .andExpect(jsonPath("$.calculationStep").value(3.5))
    }

    @Test
    fun `put v1 grid settings rejects invalid settings request`() {
        mockMvc.perform(
            put("/v1/grid/settings")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .content("""{"calculationStep": 0.0}"""),
        )
            .andExpect(status().isBadRequest)
            .andExpect(jsonPath("$.code").value("VALIDATION_ERROR"))
    }

    @Test
    fun `post v1 grid rebuild returns rebuild result`() {
        doReturn(
            GridRebuildResponseDto(
                cellsCount = 2,
                rebuiltRequestProjectionsCount = 2,
                rebuiltAt = LocalDateTime.of(2026, 5, 20, 10, 15, 30),
            )
        ).`when`(gridRebuildService).rebuildGrid()

        mockMvc.perform(post("/v1/grid/rebuild").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk)
            .andExpect(jsonPath("$.cellsCount").value(2))
            .andExpect(jsonPath("$.rebuiltRequestProjectionsCount").value(2))
            .andExpect(jsonPath("$.rebuiltAt").value("2026-05-20T10:15:30"))
    }

    @Test
    fun `post v1 grid rebuild returns conflict when lock is busy`() {
        doThrow(GridRebuildAlreadyRunningException())
            .`when`(gridRebuildService)
            .rebuildGrid()

        mockMvc.perform(post("/v1/grid/rebuild").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isConflict)
            .andExpect(jsonPath("$.code").value("GRID_REBUILD_ALREADY_RUNNING"))
    }

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