package com.ruoyi.web.controller.basic;

import com.ruoyi.RestResponse;
import com.ruoyi.basic.domain.dto.AnswerSubmitDTO;
import com.ruoyi.basic.domain.model.AnswerResultVO;
import com.ruoyi.basic.service.QuestionnaireService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@ExtendWith(MockitoExtension.class)
@DisplayName("QuestionnaireController单元测试")
class QuestionnaireControllerTest {

    private MockMvc mockMvc;

    @Mock
    private QuestionnaireService questionnaireService;

    @InjectMocks
    private QuestionnaireController questionnaireController;

    @BeforeEach
    void setUp() {
        mockMvc = MockMvcBuilders.standaloneSetup(questionnaireController).build();
    }

    /**
     * 测试正常提交答案场景
     * 覆盖：成功路径，返回包含正确答案的AnswerResultVO
     */
    @Test
    @DisplayName("提交答案成功 - 返回评估结果")
    void submit_Success_ReturnsResult() throws Exception {
        // Arrange
        AnswerSubmitDTO dto = new AnswerSubmitDTO();
        dto.setSurveyId(1L);
        dto.setSessionId("test-session-123");

        AnswerSubmitDTO.AnswerItem item1 = new AnswerSubmitDTO.AnswerItem();
        item1.setQuestionId(1L);
        item1.setOptionId(1L);

        AnswerSubmitDTO.AnswerItem item2 = new AnswerSubmitDTO.AnswerItem();
        item2.setQuestionId(2L);
        item2.setOptionId(2L);

        dto.setAnswers(Arrays.asList(item1, item2));

        AnswerResultVO resultVO = new AnswerResultVO();
        resultVO.setResultType("pinghe");
        resultVO.setResultText("恭喜您，您的体质为平和体质");
        resultVO.setConstitutionScores(new ArrayList<>());

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenReturn(resultVO);

        // Act & Assert
        mockMvc.perform(post("/api/questionnaire/submit")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"surveyId\":1,\"sessionId\":\"test-session-123\",\"answers\":[{\"questionId\":1,\"optionId\":1},{\"questionId\":2,\"optionId\":2}]}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.resultType").value("pinghe"))
                .andExpect(jsonPath("$.data.resultText").value("恭喜您，您的体质为平和体质"));

        verify(questionnaireService, times(1)).submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class));
    }

    /**
     * 测试参数校验失败场景
     * 覆盖：IllegalArgumentException异常处理路径
     */
    @Test
    @DisplayName("提交答案失败 - 参数校验失败")
    void submit_ValidationFailure_ReturnsFailure() throws Exception {
        // Arrange
        AnswerSubmitDTO dto = new AnswerSubmitDTO();
        dto.setSurveyId(null);  // 故意设置为null触发校验失败

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenThrow(new IllegalArgumentException("问卷ID不能为空"));

        // Act & Assert
        mockMvc.perform(post("/api/questionnaire/submit")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"surveyId\":null,\"answers\":[]}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(false))
                .andExpect(jsonPath("$.message").value("问卷ID不能为空"));

        verify(questionnaireService, times(1)).submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class));
    }

    /**
     * 测试通用异常处理场景
     * 覆盖：Exception异常处理路径，返回"提交失败，请稍后重试"
     */
    @Test
    @DisplayName("提交答案失败 - 系统异常")
    void submit_SystemException_ReturnsFailure() throws Exception {
        // Arrange
        AnswerSubmitDTO dto = new AnswerSubmitDTO();
        dto.setSurveyId(1L);

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenThrow(new RuntimeException("数据库连接失败"));

        // Act & Assert
        mockMvc.perform(post("/api/questionnaire/submit")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"surveyId\":1,\"answers\":[]}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(false))
                .andExpect(jsonPath("$.message").value("提交失败，请稍后重试"));

        verify(questionnaireService, times(1)).submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class));
    }

    /**
     * 测试空答案列表场景
     * 覆盖：边界情况，answers为空列表
     */
    @Test
    @DisplayName("提交答案成功 - 空答案列表")
    void submit_EmptyAnswersList_ReturnsResult() throws Exception {
        // Arrange
        AnswerSubmitDTO dto = new AnswerSubmitDTO();
        dto.setSurveyId(1L);
        dto.setSessionId(null);  // 测试sessionId为null的情况
        dto.setAnswers(Collections.emptyList());

        AnswerResultVO resultVO = new AnswerResultVO();
        resultVO.setResultType("unknown");
        resultVO.setResultText("无法判定体质类型");

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenReturn(resultVO);

        // Act & Assert
        mockMvc.perform(post("/api/questionnaire/submit")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"surveyId\":1,\"answers\":[]}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.resultType").value("unknown"));
    }

    /**
     * 测试多种异常类型场景
     * 覆盖：不同类型的RuntimeException
     */
    @Test
    @DisplayName("提交答案失败 - 问卷不存在异常")
    void submit_SurveyNotFound_ReturnsFailure() throws Exception {
        // Arrange
        AnswerSubmitDTO dto = new AnswerSubmitDTO();
        dto.setSurveyId(999L);

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenThrow(new RuntimeException("问卷不存在或未发布"));

        // Act & Assert
        mockMvc.perform(post("/api/questionnaire/submit")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"surveyId\":999,\"answers\":[{\"questionId\":1,\"optionId\":1}]}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(false))
                .andExpect(jsonPath("$.message").value("提交失败，请稍后重试"));
    }

    /**
     * 测试返回结果包含体质得分列表场景
     * 覆盖：constitutionScores不为空的正常场景
     */
    @DisplayName("提交答案成功 - 返回包含体质得分的评估结果")
    @Test
    void submit_Success_ReturnsResultWithScores() throws Exception {
        // Arrange
        com.ruoyi.basic.domain.dto.ConstitutionScoreDTO scoreDTO = new com.ruoyi.basic.domain.dto.ConstitutionScoreDTO();
        scoreDTO.setConstitutionAlias("pinghe");
        scoreDTO.setConstitutionName("平和体质");
        scoreDTO.setTotalScore(10);

        AnswerResultVO resultVO = new AnswerResultVO();
        resultVO.setResultType("pinghe");
        resultVO.setResultText("恭喜您，您的体质为平和体质");
        resultVO.setConstitutionScores(Collections.singletonList(scoreDTO));

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenReturn(resultVO);

        // Act & Assert
        mockMvc.perform(post("/api/questionnaire/submit")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"surveyId\":1,\"answers\":[{\"questionId\":1,\"optionId\":1}]}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.constitutionScores[0].constitutionAlias").value("pinghe"))
                .andExpect(jsonPath("$.data.constitutionScores[0].totalScore").value(10));
    }

    /**
     * 测试复合型体质结果场景
     * 覆盖：resultType为"multiple"的情况
     */
    @Test
    @DisplayName("提交答案成功 - 复合型体质结果")
    void submit_MultipleType_ReturnsResult() throws Exception {
        // Arrange
        AnswerResultVO resultVO = new AnswerResultVO();
        resultVO.setResultType("multiple");
        resultVO.setResultText("亲爱的伽人您好，您的体质为复合型体质典型，建议综合调理。");

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenReturn(resultVO);

        // Act & Assert
        mockMvc.perform(post("/api/questionnaire/submit")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"surveyId\":1,\"answers\":[{\"questionId\":1,\"optionId\":1}]}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.resultType").value("multiple"));
    }

    /**
     * 测试偏颇体质结果场景
     * 覆盖：resultType为"qixu"等偏颇体质
     */
    @Test
    @DisplayName("提交答案成功 - 偏颇体质结果")
    void submit_BiasedType_ReturnsResult() throws Exception {
        // Arrange
        AnswerResultVO resultVO = new AnswerResultVO();
        resultVO.setResultType("qixu");
        resultVO.setResultText("亲爱的伽人您好，您的体质为典型气虚体质。");

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenReturn(resultVO);

        // Act & Assert
        mockMvc.perform(post("/api/questionnaire/submit")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"surveyId\":1,\"answers\":[{\"questionId\":1,\"optionId\":1}]}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.resultType").value("qixu"));
    }

    /**
     * 测试倾向体质结果场景
     * 覆盖：resultType为"qixu_tendency"等倾向体质
     */
    @Test
    @DisplayName("提交答案成功 - 倾向体质结果")
    void submit_TendencyType_ReturnsResult() throws Exception {
        // Arrange
        AnswerResultVO resultVO = new AnswerResultVO();
        resultVO.setResultType("qixu_tendency");
        resultVO.setResultText("亲爱的伽人您好，您的体质为气虚倾向体质。");

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenReturn(resultVO);

        // Act & Assert
        mockMvc.perform(post("/api/questionnaire/submit")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"surveyId\":1,\"answers\":[{\"questionId\":1,\"optionId\":1}]}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.resultType").value("qixu_tendency"));
    }

    /**
     * 测试直接调用controller方法 - 成功场景
     */
    @Test
    @DisplayName("直接调用submit方法 - 成功场景")
    void submit_DirectCall_Success() {
        // Arrange
        AnswerSubmitDTO dto = new AnswerSubmitDTO();
        dto.setSurveyId(1L);
        dto.setSessionId("session-123");

        AnswerSubmitDTO.AnswerItem item = new AnswerSubmitDTO.AnswerItem();
        item.setQuestionId(1L);
        item.setOptionId(1L);
        dto.setAnswers(Collections.singletonList(item));

        AnswerResultVO expectedResult = new AnswerResultVO();
        expectedResult.setResultType("pinghe");
        expectedResult.setResultText("测试结果");

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(eq(dto), any(HttpServletRequest.class)))
                .thenReturn(expectedResult);

        // Act
        RestResponse response = questionnaireController.submit(dto, request);

        // Assert
        assertNotNull(response);
        assertTrue(response.getSuccess());
        assertEquals("成功", response.getMessage());
        assertNotNull(response.get("data"));

        AnswerResultVO result = (AnswerResultVO) response.get("data");
        assertEquals("pinghe", result.getResultType());
        assertEquals("测试结果", result.getResultText());
    }

    /**
     * 测试直接调用controller方法 - 参数校验失败场景
     */
    @Test
    @DisplayName("直接调用submit方法 - 参数校验失败场景")
    void submit_DirectCall_ValidationFailure() {
        // Arrange
        AnswerSubmitDTO dto = new AnswerSubmitDTO();
        dto.setSurveyId(null);  // 触发校验失败
        dto.setAnswers(Collections.emptyList());

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenThrow(new IllegalArgumentException("问卷ID不能为空"));

        // Act
        RestResponse response = questionnaireController.submit(dto, request);

        // Assert
        assertNotNull(response);
        assertEquals(false, response.getSuccess());
        assertEquals("问卷ID不能为空", response.getMessage());
    }

    /**
     * 测试直接调用controller方法 - 系统异常场景
     */
    @Test
    @DisplayName("直接调用submit方法 - 系统异常场景")
    void submit_DirectCall_SystemException() {
        // Arrange
        AnswerSubmitDTO dto = new AnswerSubmitDTO();
        dto.setSurveyId(1L);
        dto.setAnswers(Collections.emptyList());

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenThrow(new RuntimeException("数据库异常"));

        // Act
        RestResponse response = questionnaireController.submit(dto, request);

        // Assert
        assertNotNull(response);
        assertEquals(false, response.getSuccess());
        assertEquals("提交失败，请稍后重试", response.getMessage());
    }

    /**
     * 测试多题目录入场景
     * 覆盖：多个答案项的情况
     */
    @Test
    @DisplayName("提交多题目答案 - 正常场景")
    void submit_MultipleQuestions_Success() throws Exception {
        // Arrange
        List<AnswerSubmitDTO.AnswerItem> answers = new ArrayList<>();
        for (long i = 1; i <= 10; i++) {
            AnswerSubmitDTO.AnswerItem item = new AnswerSubmitDTO.AnswerItem();
            item.setQuestionId(i);
            item.setOptionId(i);
            answers.add(item);
        }

        AnswerResultVO resultVO = new AnswerResultVO();
        resultVO.setResultType("qixu");
        resultVO.setResultText("气虚体质");

        HttpServletRequest request = mock(HttpServletRequest.class);
        when(questionnaireService.submitAnswers(any(AnswerSubmitDTO.class), any(HttpServletRequest.class)))
                .thenReturn(resultVO);

        // Act & Assert - 使用MockMvc直接测试
        mockMvc.perform(post("/api/questionnaire/submit")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"surveyId\":1,\"answers\":[{\"questionId\":1,\"optionId\":1},{\"questionId\":2,\"optionId\":2},{\"questionId\":3,\"optionId\":3},{\"questionId\":4,\"optionId\":4},{\"questionId\":5,\"optionId\":5}]}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true));
    }
}
