package ai.decompile.job.controller;

import static org.mockito.ArgumentMatchers.eq;
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.job.model.dto.JobListResponse;
import ai.decompile.job.model.dto.JobResponse;
import ai.decompile.job.model.enums.JobStatus;
import ai.decompile.job.model.enums.JobType;
import ai.decompile.job.service.JobService;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(JobController.class)
class JobControllerTest {

  @Autowired private MockMvc mockMvc;

  @MockitoBean private JobService jobService;

  @Test
  void getJobsShouldReturn200WithList() throws Exception {
    var jobId = UUID.randomUUID();
    var projectId = UUID.randomUUID();
    var binaryId = UUID.randomUUID();
    var jobResponse =
        new JobResponse(
            jobId,
            JobType.ANALYZE_FILE,
            JobStatus.ENQUEUED,
            projectId,
            binaryId,
            "binary.exe",
            "Test Project",
            null,
            null,
            null,
            0,
            Instant.now(),
            Instant.now());
    when(jobService.getJobs(eq(projectId), isNull(), isNull(), isNull()))
        .thenReturn(new JobListResponse(List.of(jobResponse), 1, 2, 3, 4));

    mockMvc
        .perform(get("/jobs").param("projectId", projectId.toString()))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.jobs[0].id").value(jobId.toString()))
        .andExpect(jsonPath("$.jobs[0].type").value("ANALYZE_FILE"))
        .andExpect(jsonPath("$.jobs[0].status").value("ENQUEUED"))
        .andExpect(jsonPath("$.jobs[0].projectId").value(projectId.toString()))
        .andExpect(jsonPath("$.jobs[0].binaryId").value(binaryId.toString()))
        .andExpect(jsonPath("$.running").value(1))
        .andExpect(jsonPath("$.queued").value(2))
        .andExpect(jsonPath("$.completed").value(3))
        .andExpect(jsonPath("$.failed").value(4));
  }

  @Test
  void getJobsShouldReturn200WithEmptyList() throws Exception {
    var projectId = UUID.randomUUID();
    when(jobService.getJobs(eq(projectId), isNull(), isNull(), isNull()))
        .thenReturn(new JobListResponse(List.of(), 0, 0, 0, 0));

    mockMvc
        .perform(get("/jobs").param("projectId", projectId.toString()))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.jobs").isArray())
        .andExpect(jsonPath("$.jobs").isEmpty())
        .andExpect(jsonPath("$.running").value(0));
  }

  @Test
  void getJobsShouldFilterByStatus() throws Exception {
    var projectId = UUID.randomUUID();
    when(jobService.getJobs(eq(projectId), eq(JobStatus.ENQUEUED), isNull(), isNull()))
        .thenReturn(new JobListResponse(List.of(), 0, 0, 0, 0));

    mockMvc
        .perform(get("/jobs").param("projectId", projectId.toString()).param("status", "ENQUEUED"))
        .andExpect(status().isOk());
  }

  @Test
  void getJobsShouldFilterByType() throws Exception {
    var projectId = UUID.randomUUID();
    when(jobService.getJobs(eq(projectId), isNull(), eq(JobType.ANALYZE_FILE), isNull()))
        .thenReturn(new JobListResponse(List.of(), 0, 0, 0, 0));

    mockMvc
        .perform(
            get("/jobs").param("projectId", projectId.toString()).param("type", "ANALYZE_FILE"))
        .andExpect(status().isOk());
  }

  @Test
  void getJobShouldReturn200() throws Exception {
    var jobId = UUID.randomUUID();
    var projectId = UUID.randomUUID();
    var binaryId = UUID.randomUUID();
    var response =
        new JobResponse(
            jobId,
            JobType.ANALYZE_FILE,
            JobStatus.COMPLETED,
            projectId,
            binaryId,
            "binary.exe",
            "Test Project",
            null,
            null,
            null,
            0,
            Instant.now(),
            Instant.now());
    when(jobService.getJob(jobId)).thenReturn(response);

    mockMvc
        .perform(get("/jobs/{jobId}", jobId))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.id").value(jobId.toString()))
        .andExpect(jsonPath("$.status").value("COMPLETED"));
  }

  @Test
  void cancelJobShouldReturn200() throws Exception {
    var jobId = UUID.randomUUID();
    var projectId = UUID.randomUUID();
    var binaryId = UUID.randomUUID();
    var response =
        new JobResponse(
            jobId,
            JobType.ANALYZE_FILE,
            JobStatus.CANCELED,
            projectId,
            binaryId,
            "binary.exe",
            "Test Project",
            null,
            null,
            null,
            0,
            Instant.now(),
            Instant.now());
    when(jobService.cancelJob(jobId)).thenReturn(response);

    mockMvc
        .perform(post("/jobs/{jobId}/cancel", jobId))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.id").value(jobId.toString()))
        .andExpect(jsonPath("$.status").value("CANCELED"));
  }

  @Test
  void getJobShouldReturn404OnNotFound() throws Exception {
    var jobId = UUID.randomUUID();
    when(jobService.getJob(jobId))
        .thenThrow(new ai.decompile.common.exception.NotFoundException("not found"));

    mockMvc
        .perform(get("/jobs/{jobId}", jobId))
        .andExpect(status().isNotFound())
        .andExpect(jsonPath("$.status").value(404));
  }
}
