package ai.decompile.workspace.controller;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import ai.decompile.common.exception.ConflictException;
import ai.decompile.common.exception.NotFoundException;
import ai.decompile.workspace.model.dto.WorkspaceRequest;
import ai.decompile.workspace.model.dto.WorkspaceResponse;
import ai.decompile.workspace.service.WorkspaceService;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(WorkspaceController.class)
class WorkspaceControllerTest {

  @Autowired private MockMvc mockMvc;

  @MockitoBean private WorkspaceService workspaceService;

  private final ObjectMapper objectMapper = new ObjectMapper();

  @Test
  void getWorkspacesShouldReturn200WithList() throws Exception {
    var id = UUID.randomUUID();
    var response = new WorkspaceResponse(id, "Test", "Desc", Instant.now(), 0);
    when(workspaceService.getWorkspaces(isNull())).thenReturn(List.of(response));

    mockMvc
        .perform(get("/workspaces"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$[0].id").value(id.toString()))
        .andExpect(jsonPath("$[0].name").value("Test"))
        .andExpect(jsonPath("$[0].description").value("Desc"));
  }

  @Test
  void getWorkspacesShouldFilterByName() throws Exception {
    var id = UUID.randomUUID();
    var response = new WorkspaceResponse(id, "Filtered", "Desc", Instant.now(), 0);
    when(workspaceService.getWorkspaces(eq("fil"))).thenReturn(List.of(response));

    mockMvc
        .perform(get("/workspaces").param("name", "fil"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$[0].id").value(id.toString()))
        .andExpect(jsonPath("$[0].name").value("Filtered"));
  }

  @Test
  void getWorkspaceShouldReturn200() throws Exception {
    var id = UUID.randomUUID();
    var response = new WorkspaceResponse(id, "Single", "Desc", Instant.now(), 0);
    when(workspaceService.getWorkspaceById(id)).thenReturn(response);

    mockMvc
        .perform(get("/workspaces/{id}", id))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.id").value(id.toString()))
        .andExpect(jsonPath("$.name").value("Single"))
        .andExpect(jsonPath("$.description").value("Desc"));
  }

  @Test
  void getWorkspaceShouldReturn404OnNotFound() throws Exception {
    var id = UUID.randomUUID();
    when(workspaceService.getWorkspaceById(id))
        .thenThrow(new NotFoundException("Workspace with id '" + id + "' not found"));

    mockMvc
        .perform(get("/workspaces/{id}", id))
        .andExpect(status().isNotFound())
        .andExpect(jsonPath("$.status").value(404))
        .andExpect(jsonPath("$.error").value("Not Found"));
  }

  @Test
  void createWorkspaceShouldReturn200() throws Exception {
    var id = UUID.randomUUID();
    var request = new WorkspaceRequest("New Workspace", "A description");
    var response = new WorkspaceResponse(id, "New Workspace", "A description", Instant.now(), 0);
    when(workspaceService.createWorkspace(any())).thenReturn(response);

    mockMvc
        .perform(
            post("/workspaces")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.id").value(id.toString()))
        .andExpect(jsonPath("$.name").value("New Workspace"));
  }

  @Test
  void createWorkspaceShouldReturn409OnConflict() throws Exception {
    var request = new WorkspaceRequest("Duplicate", "desc");
    when(workspaceService.createWorkspace(any()))
        .thenThrow(new ConflictException("A workspace with the name 'Duplicate' already exists."));

    mockMvc
        .perform(
            post("/workspaces")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isConflict())
        .andExpect(jsonPath("$.status").value(409))
        .andExpect(jsonPath("$.error").value("Conflict"));
  }

  @Test
  void updateWorkspaceShouldReturn200() throws Exception {
    var id = UUID.randomUUID();
    var request = new WorkspaceRequest("Updated", "Updated desc");
    var response = new WorkspaceResponse(id, "Updated", "Updated desc", Instant.now(), 0);
    when(workspaceService.updateWorkspace(eq(id), any())).thenReturn(response);

    mockMvc
        .perform(
            put("/workspaces/{id}", id)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.name").value("Updated"));
  }

  @Test
  void updateWorkspaceShouldReturn404OnNotFound() throws Exception {
    var id = UUID.randomUUID();
    var request = new WorkspaceRequest("Updated", "Updated desc");
    when(workspaceService.updateWorkspace(eq(id), any()))
        .thenThrow(new NotFoundException("Workspace with id '" + id + "' not found"));

    mockMvc
        .perform(
            put("/workspaces/{id}", id)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isNotFound())
        .andExpect(jsonPath("$.status").value(404))
        .andExpect(jsonPath("$.error").value("Not Found"));
  }

  @Test
  void deleteWorkspaceShouldReturn200() throws Exception {
    var id = UUID.randomUUID();
    doNothing().when(workspaceService).deleteWorkspace(id);

    mockMvc.perform(delete("/workspaces/{id}", id)).andExpect(status().isOk());
  }

  @Test
  void deleteWorkspaceShouldReturn404OnNotFound() throws Exception {
    var id = UUID.randomUUID();
    doThrow(new NotFoundException("Workspace with id '" + id + "' not found"))
        .when(workspaceService)
        .deleteWorkspace(id);

    mockMvc
        .perform(delete("/workspaces/{id}", id))
        .andExpect(status().isNotFound())
        .andExpect(jsonPath("$.status").value(404))
        .andExpect(jsonPath("$.error").value("Not Found"));
  }
}
