package cn.yunzhixue.ability.center.examsprint.adapter.http;

import cn.yunzhixue.ability.center.examsprint.application.report.ExamSprintReportApplicationService;
import cn.yunzhixue.ability.center.examsprint.application.report.ExamSprintReportApplicationService.ReportDownloadContent;
import cn.yunzhixue.ability.center.examsprint.application.report.ExamSprintReportApplicationService.ReportHtmlPreviewContent;
import cn.yunzhixue.ability.center.examsprint.contracts.report.CreateExamSprintReportResponse;
import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportDetailResponse;
import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportGenerationStatus;
import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType;
import cn.yunzhixue.ability.center.kernel.BusinessException;
import cn.yunzhixue.ability.center.kernel.ErrorCode;
import cn.yunzhixue.ability.center.GlobalExceptionHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;

import java.nio.charset.StandardCharsets;
import java.time.Instant;

import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
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;

@WebMvcTest(ExamSprintReportController.class)
@Import({ExamSprintReportController.class, GlobalExceptionHandler.class})
class ExamSprintReportControllerWebMvcTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private ExamSprintReportApplicationService applicationService;

    @Test
    void createReportUsesExamSprintReportsEndpoint() throws Exception {
        given(applicationService.createReport(any())).willReturn(new CreateExamSprintReportResponse(
                "report-001",
                ExamSprintReportType.OUTLOOK,
                ExamSprintReportGenerationStatus.PENDING,
                Instant.parse("2026-01-01T00:00:00Z"),
                Instant.parse("2026-01-02T00:00:00Z")));

        String requestJson = """
                {
                  "reportType": "OUTLOOK",
                  "payload": {
                    "reportMetadata": {
                      "reportVersionLabel": "2026 词汇展望报告",
                      "learnerName": "李同学",
                      "targetExamName": "雅思 6.5",
                      "sprintPeriodLabel": "2026 春季冲刺",
                      "authorName": "Ability Bot"
                    }
                  }
                }
                """;

        mockMvc.perform(post("/api/exam-sprint/reports")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(requestJson))
                .andExpect(status().isAccepted())
                .andExpect(jsonPath("$.data.reportId").value("report-001"))
                .andExpect(jsonPath("$.data.reportType").value("OUTLOOK"))
                .andExpect(jsonPath("$.data.generationStatus").value("PENDING"));
    }

    @Test
    void createReportReturnsValidationErrorWhenJsonIsMalformed() throws Exception {
        mockMvc.perform(post("/api/exam-sprint/reports")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"reportType\":\"OUTLOOK\",\"payload\":{]"))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("VALIDATION_ERROR"));

        verifyNoInteractions(applicationService);
    }

    @Test
    void createReportReturnsValidationErrorWhenReportTypeEnumIsUnknown() throws Exception {
        String requestJson = """
                {
                  "reportType": "UNKNOWN",
                  "payload": {
                    "reportMetadata": {
                      "reportVersionLabel": "2026 词汇展望报告",
                      "learnerName": "李同学",
                      "targetExamName": "雅思 6.5",
                      "sprintPeriodLabel": "2026 春季冲刺",
                      "authorName": "Ability Bot"
                    }
                  }
                }
                """;

        mockMvc.perform(post("/api/exam-sprint/reports")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(requestJson))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("VALIDATION_ERROR"));

        verifyNoInteractions(applicationService);
    }

    @Test
    void downloadReportUsesReportIdInsteadOfStorageKey() throws Exception {
        given(applicationService.getReport(eq("report-001"))).willReturn(new ExamSprintReportDetailResponse(
                "report-001",
                ExamSprintReportType.OUTLOOK,
                ExamSprintReportGenerationStatus.SUCCESS,
                Instant.parse("2026-01-01T00:00:00Z"),
                Instant.parse("2026-01-01T00:05:00Z"),
                Instant.parse("2026-01-02T00:00:00Z"),
                "/api/exam-sprint/reports/report-001/download",
                "/api/exam-sprint/reports/report-001/preview/html",
                null));
        given(applicationService.downloadReport("report-001")).willReturn(new ReportDownloadContent(
                "exam-sprint-outlook-report-report-001.pdf",
                "%PDF".getBytes(StandardCharsets.US_ASCII),
                "application/pdf"));

        mockMvc.perform(get("/api/exam-sprint/reports/{reportId}", "report-001"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.reportId").value("report-001"))
                .andExpect(jsonPath("$.data.downloadUrl").value("/api/exam-sprint/reports/report-001/download"));

        mockMvc.perform(get("/api/exam-sprint/reports/{reportId}/download", "report-001"))
                .andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_PDF));
    }

    @Test
    void previewHtmlReturnsTextHtmlForSucceededReport() throws Exception {
        given(applicationService.previewReportHtml("report-001")).willReturn(new ReportHtmlPreviewContent(
                "<html><body>高考英语临考突击学习成果报告</body></html>",
                "text/html;charset=UTF-8"));

        mockMvc.perform(get("/api/exam-sprint/reports/{reportId}/preview/html", "report-001"))
                .andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
                .andExpect(content().string(org.hamcrest.Matchers.containsString("高考英语临考突击学习成果报告")));
    }

    @Test
    void getReportMapsNewKernelBusinessExceptionToBusinessStatusCode() throws Exception {
        given(applicationService.getReport(eq("missing-report")))
                .willThrow(new BusinessException(ErrorCode.REPORT_NOT_FOUND));

        mockMvc.perform(get("/api/exam-sprint/reports/{reportId}", "missing-report"))
                .andExpect(status().isNotFound())
                .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.code").value("REPORT_NOT_FOUND"));
    }

    @SpringBootConfiguration
    @EnableAutoConfiguration
    static class TestApplication {
    }
}
