package com.kt.ai.test.unit.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kt.ai.controller.InternalJobController;
import com.kt.ai.exception.JobNotFoundException;
import com.kt.ai.model.dto.response.JobStatusResponse;
import com.kt.ai.model.enums.JobStatus;
import com.kt.ai.service.CacheService;
import com.kt.ai.service.JobStatusService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDateTime;

import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * InternalJobController 단위 테스트
 *
 * @author AI Service Team
 * @since 1.0.0
 */
@WebMvcTest(controllers = InternalJobController.class,
        excludeAutoConfiguration = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
@DisplayName("InternalJobController 단위 테스트")
class InternalJobControllerUnitTest {

    // Constants
    private static final String VALID_JOB_ID = "job-123";
    private static final String INVALID_JOB_ID = "job-999";
    private static final String BASE_URL = "/api/v1/ai-service/internal/jobs";

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private JobStatusService jobStatusService;

    @MockBean
    private CacheService cacheService;

    private JobStatusResponse sampleJobStatusResponse;

    @BeforeEach
    void setUp() {
        sampleJobStatusResponse = JobStatusResponse.builder()
                .jobId(VALID_JOB_ID)
                .status(JobStatus.PROCESSING)
                .progress(50)
                .message("AI 추천 생성 중 (50%)")
                .createdAt(LocalDateTime.now())
                .build();
    }

    // ========== GET /{jobId}/status 테스트 ==========

    @Test
    @DisplayName("Given existing job, When get status, Then return 200 with job status")
    void givenExistingJob_whenGetStatus_thenReturn200WithJobStatus() throws Exception {
        // Given
        when(jobStatusService.getJobStatus(VALID_JOB_ID)).thenReturn(sampleJobStatusResponse);

        // When & Then
        mockMvc.perform(get(BASE_URL + "/{jobId}/status", VALID_JOB_ID)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.jobId", is(VALID_JOB_ID)))
                .andExpect(jsonPath("$.status", is("PROCESSING")))
                .andExpect(jsonPath("$.progress", is(50)))
                .andExpect(jsonPath("$.message", is("AI 추천 생성 중 (50%)")))
                .andExpect(jsonPath("$.createdAt", notNullValue()));

        verify(jobStatusService, times(1)).getJobStatus(VALID_JOB_ID);
    }

    @Test
    @DisplayName("Given non-existing job, When get status, Then return 404")
    void givenNonExistingJob_whenGetStatus_thenReturn404() throws Exception {
        // Given
        when(jobStatusService.getJobStatus(INVALID_JOB_ID))
                .thenThrow(new JobNotFoundException(INVALID_JOB_ID));

        // When & Then
        mockMvc.perform(get(BASE_URL + "/{jobId}/status", INVALID_JOB_ID)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("$.code", is("JOB_NOT_FOUND")))
                .andExpect(jsonPath("$.message", containsString(INVALID_JOB_ID)));

        verify(jobStatusService, times(1)).getJobStatus(INVALID_JOB_ID);
    }

    @Test
    @DisplayName("Given completed job, When get status, Then return COMPLETED status with 100% progress")
    void givenCompletedJob_whenGetStatus_thenReturnCompletedStatus() throws Exception {
        // Given
        JobStatusResponse completedResponse = JobStatusResponse.builder()
                .jobId(VALID_JOB_ID)
                .status(JobStatus.COMPLETED)
                .progress(100)
                .message("AI 추천 완료")
                .createdAt(LocalDateTime.now())
                .build();

        when(jobStatusService.getJobStatus(VALID_JOB_ID)).thenReturn(completedResponse);

        // When & Then
        mockMvc.perform(get(BASE_URL + "/{jobId}/status", VALID_JOB_ID)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status", is("COMPLETED")))
                .andExpect(jsonPath("$.progress", is(100)));

        verify(jobStatusService, times(1)).getJobStatus(VALID_JOB_ID);
    }

    @Test
    @DisplayName("Given failed job, When get status, Then return FAILED status")
    void givenFailedJob_whenGetStatus_thenReturnFailedStatus() throws Exception {
        // Given
        JobStatusResponse failedResponse = JobStatusResponse.builder()
                .jobId(VALID_JOB_ID)
                .status(JobStatus.FAILED)
                .progress(0)
                .message("AI API 호출 실패")
                .createdAt(LocalDateTime.now())
                .build();

        when(jobStatusService.getJobStatus(VALID_JOB_ID)).thenReturn(failedResponse);

        // When & Then
        mockMvc.perform(get(BASE_URL + "/{jobId}/status", VALID_JOB_ID)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status", is("FAILED")))
                .andExpect(jsonPath("$.progress", is(0)))
                .andExpect(jsonPath("$.message", containsString("실패")));

        verify(jobStatusService, times(1)).getJobStatus(VALID_JOB_ID);
    }

    // ========== 디버그 엔드포인트 테스트 (선택사항) ==========

    @Test
    @DisplayName("Given valid jobId, When create test job, Then return 200 with test data")
    void givenValidJobId_whenCreateTestJob_thenReturn200WithTestData() throws Exception {
        // Given
        doNothing().when(jobStatusService).updateJobStatus(anyString(), org.mockito.ArgumentMatchers.any(JobStatus.class), anyString());
        when(cacheService.getJobStatus(VALID_JOB_ID)).thenReturn(sampleJobStatusResponse);

        // When & Then
        mockMvc.perform(get(BASE_URL + "/debug/create-test-job/{jobId}", VALID_JOB_ID)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success", is(true)))
                .andExpect(jsonPath("$.jobId", is(VALID_JOB_ID)))
                .andExpect(jsonPath("$.saved", is(true)))
                .andExpect(jsonPath("$.additionalSamples", notNullValue()));

        // updateJobStatus가 4번 호출되어야 함 (main + 3 additional samples)
        verify(jobStatusService, times(4)).updateJobStatus(anyString(), org.mockito.ArgumentMatchers.any(JobStatus.class), anyString());
        verify(cacheService, times(1)).getJobStatus(VALID_JOB_ID);
    }
}
