package ai.decompile.project.controller;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import ai.decompile.workspace.model.dto.ProjectRequest;
import ai.decompile.workspace.model.dto.ProjectResponse;
import ai.decompile.workspace.model.dto.WorkspaceRequest;
import ai.decompile.workspace.model.dto.WorkspaceResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.EntityManager;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
@Transactional
class ProjectIntegrationTest {

  @Autowired private MockMvc mockMvc;

  @Autowired private EntityManager entityManager;

  private final ObjectMapper objectMapper = new ObjectMapper();

  @Test
  void getProjectsShouldReturnEmptyList() throws Exception {
    var workspaceId = createWorkspace("Empty Workspace");

    mockMvc
        .perform(get("/workspaces/{workspaceId}/projects", workspaceId))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$").isArray())
        .andExpect(jsonPath("$").isEmpty());
  }

  @Test
  void getProjectsShouldFilterByName() throws Exception {
    var workspaceId = createWorkspace("Filter WS");

    mockMvc
        .perform(
            post("/workspaces/{workspaceId}/projects", workspaceId)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(new ProjectRequest("Alpha", "desc"))))
        .andExpect(status().isOk());

    mockMvc
        .perform(
            post("/workspaces/{workspaceId}/projects", workspaceId)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(new ProjectRequest("Beta", "desc"))))
        .andExpect(status().isOk());

    mockMvc
        .perform(get("/workspaces/{workspaceId}/projects", workspaceId).param("name", "alpha"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.length()").value(1))
        .andExpect(jsonPath("$[0].name").value("Alpha"));

    mockMvc
        .perform(get("/workspaces/{workspaceId}/projects", workspaceId))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.length()").value(2));
  }

  @Test
  void fullCrudFlow() throws Exception {
    var workspaceId = createWorkspace("CRUD Workspace");

    var request = new ProjectRequest("CRUD Project", "CRUD Description");

    var createdJson =
        mockMvc
            .perform(
                post("/workspaces/{workspaceId}/projects", workspaceId)
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("CRUD Project"))
            .andExpect(jsonPath("$.workspaceId").value(workspaceId.toString()))
            .andReturn()
            .getResponse()
            .getContentAsString();

    var created = objectMapper.readValue(createdJson, ProjectResponse.class);

    var updateRequest = new ProjectRequest("CRUD Updated", "Updated Description");
    mockMvc
        .perform(
            put("/projects/{projectId}", created.id())
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(updateRequest)))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.name").value("CRUD Updated"));

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

    mockMvc.perform(delete("/projects/{projectId}", created.id())).andExpect(status().isNotFound());
  }

  @Test
  void createProjectShouldReturn409OnDuplicateNameInSameWorkspace() throws Exception {
    var workspaceId = createWorkspace("Dup WS");

    var request = new ProjectRequest("Unique Name", "desc");

    mockMvc
        .perform(
            post("/workspaces/{workspaceId}/projects", workspaceId)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isOk());

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

  @Test
  void sameNameInDifferentWorkspacesShouldSucceed() throws Exception {
    var ws1 = createWorkspace("Workspace One");
    var ws2 = createWorkspace("Workspace Two");

    var request = new ProjectRequest("Same Name", "desc");

    mockMvc
        .perform(
            post("/workspaces/{workspaceId}/projects", ws1)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isOk());

    mockMvc
        .perform(
            post("/workspaces/{workspaceId}/projects", ws2)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isOk());
  }

  @Test
  void createProjectShouldReturn404WhenWorkspaceDoesNotExist() throws Exception {
    var nonExistentId = UUID.randomUUID();
    var request = new ProjectRequest("Project", "desc");

    mockMvc
        .perform(
            post("/workspaces/{workspaceId}/projects", nonExistentId)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isNotFound())
        .andExpect(jsonPath("$.status").value(404));
  }

  @Test
  void nonexistentProjectShouldReturn404() throws Exception {
    var workspaceId = createWorkspace("WS");
    var nonExistentId = UUID.randomUUID();
    var request = new ProjectRequest("Update", "desc");

    mockMvc
        .perform(
            put("/projects/{projectId}", nonExistentId)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isNotFound())
        .andExpect(jsonPath("$.status").value(404));

    mockMvc
        .perform(delete("/projects/{projectId}", nonExistentId))
        .andExpect(status().isNotFound())
        .andExpect(jsonPath("$.status").value(404));
  }

  @Test
  void workspaceListShouldReturnProjectCount() throws Exception {
    var workspaceId = createWorkspace("Count WS");

    mockMvc
        .perform(get("/workspaces"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$[0].projectCount").value(0));

    mockMvc
        .perform(
            post("/workspaces/{workspaceId}/projects", workspaceId)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(new ProjectRequest("P1", "desc"))))
        .andExpect(status().isOk());

    mockMvc
        .perform(
            post("/workspaces/{workspaceId}/projects", workspaceId)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(new ProjectRequest("P2", "desc"))))
        .andExpect(status().isOk());

    entityManager.flush();
    entityManager.clear();

    mockMvc
        .perform(get("/workspaces"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$[0].projectCount").value(2));
  }

  @Test
  void listProjectsForSpecificWorkspace() throws Exception {
    var ws1 = createWorkspace("WS1");
    var ws2 = createWorkspace("WS2");

    mockMvc
        .perform(
            post("/workspaces/{workspaceId}/projects", ws1)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(new ProjectRequest("Project A", "desc"))))
        .andExpect(status().isOk());

    mockMvc
        .perform(
            post("/workspaces/{workspaceId}/projects", ws2)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(new ProjectRequest("Project B", "desc"))))
        .andExpect(status().isOk());

    mockMvc
        .perform(get("/workspaces/{workspaceId}/projects", ws1))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.length()").value(1))
        .andExpect(jsonPath("$[0].name").value("Project A"));

    mockMvc
        .perform(get("/workspaces/{workspaceId}/projects", ws2))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.length()").value(1))
        .andExpect(jsonPath("$[0].name").value("Project B"));
  }

  private UUID createWorkspace(String name) throws Exception {
    var request = new WorkspaceRequest(name, "description");
    var json =
        mockMvc
            .perform(
                post("/workspaces")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isOk())
            .andReturn()
            .getResponse()
            .getContentAsString();
    return objectMapper.readValue(json, WorkspaceResponse.class).id();
  }
}
