package com.ruoyi.lsfx.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.lsfx.client.CreditParseClient;
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
import com.ruoyi.lsfx.exception.LsfxApiException;
import com.ruoyi.lsfx.util.HttpUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class CreditParseControllerTest {

    @Mock
    private CreditParseClient client;

    @InjectMocks
    private CreditParseController controller;

    @Test
    void parse_shouldRejectBlankRemotePath() {
        AjaxResult result = controller.parse(null, null);
        assertEquals(500, result.get("code"));
    }

    @Test
    void shouldUseDefaultModelWhenMissing() {
        CreditParseInvokeResponse response = new CreditParseInvokeResponse();
        response.setSuccess(true);
        response.setCode(10000);

        String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
        when(client.parse(eq("LXCUSTALL"), eq(remotePath))).thenReturn(response);

        AjaxResult result = controller.parse(remotePath, null);

        assertEquals(200, result.get("code"));
        assertSame(response, result.get("data"));
    }

    @Test
    void shouldReturnAjaxErrorWhenClientThrows() {
        when(client.parse(anyString(), anyString()))
            .thenThrow(new LsfxApiException("超时"));

        AjaxResult result = controller.parse("http://127.0.0.1:62318/profile/credit-html/a.html", null);

        assertEquals(500, result.get("code"));
    }

    @Test
    @SuppressWarnings({"unchecked", "rawtypes"})
    void creditParseClient_shouldInitiateAndQueryResultWithSameSerialNum() throws Exception {
        HttpUtil httpUtil = mock(HttpUtil.class);
        CreditParseClient parseClient = new CreditParseClient();
        ObjectMapper objectMapper = new ObjectMapper();
        ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
        ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
        ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult");
        ReflectionTestUtils.setField(parseClient, "orgCode", "999000");
        ReflectionTestUtils.setField(parseClient, "runType", "1");
        ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
        ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);

        String payload = "{\"lx_header\":{\"query_cert_no\":\"330101199001010011\",\"query_cust_name\":\"张三\",\"report_time\":\"2026-03-24\"},\"lx_debt\":{\"uncle_bank_house_bal\":\"1\"},\"lx_publictype\":{\"civil_cnt\":1}}";

        when(httpUtil.postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeature"),
                org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
                isNull()
        )).thenReturn(initiateSuccessResponse());
        when(httpUtil.postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
                org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
                isNull()
        )).thenReturn(resultSuccessResponse(objectMapper, payload, "ERR_SHOULD_IGNORE"));

        String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
        CreditParseInvokeResponse actual = parseClient.parse(remotePath);

        assertEquals(true, actual.getSuccess());
        assertEquals(10000, actual.getCode());
        assertEquals("330101199001010011", actual.getData().getMappingOutputFields()
                .getPayload().getLxHeader().get("query_cert_no"));
        ArgumentCaptor<Map<String, Object>> initiateParamsCaptor = ArgumentCaptor.forClass((Class) Map.class);
        verify(httpUtil).postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeature"),
                initiateParamsCaptor.capture(),
                isNull()
        );
        ArgumentCaptor<Map<String, Object>> resultParamsCaptor = ArgumentCaptor.forClass((Class) Map.class);
        verify(httpUtil).postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
                resultParamsCaptor.capture(),
                isNull()
        );

        Map<String, Object> initiateParams = initiateParamsCaptor.getValue();
        Map<String, Object> resultParams = resultParamsCaptor.getValue();
        assertNotNull(initiateParams.get("serialNum"));
        assertTrue(initiateParams.get("serialNum").toString().startsWith("CCDI_CREDIT_"));
        assertEquals(initiateParams.get("serialNum"), resultParams.get("serialNum"));
        assertEquals("999000", initiateParams.get("orgCode"));
        assertEquals("999000", resultParams.get("orgCode"));
        assertEquals("1", initiateParams.get("runType"));
        assertEquals("1", resultParams.get("runType"));
        assertEquals(remotePath, initiateParams.get("remotePath"));
        assertEquals("LXCUSTALL", initiateParams.get("model"));
        assertEquals(false, resultParams.containsKey("remotePath"));
        assertEquals(false, resultParams.containsKey("model"));
    }

    @Test
    @SuppressWarnings({"unchecked", "rawtypes"})
    void creditParseClient_shouldRetryEmptyPayloadFiveTimesWithTwoSecondInterval() throws Exception {
        HttpUtil httpUtil = mock(HttpUtil.class);
        TestableCreditParseClient parseClient = new TestableCreditParseClient();
        ObjectMapper objectMapper = new ObjectMapper();
        ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
        ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
        ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult");
        ReflectionTestUtils.setField(parseClient, "orgCode", "999000");
        ReflectionTestUtils.setField(parseClient, "runType", "1");
        ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
        ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);

        String emptyPayloadResponse = "{\"success\":true,\"code\":10000,\"data\":{\"status\":1,\"reasonCode\":200,\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"ERR_SHOULD_IGNORE\"}}}";
        String payload = "{\"lx_header\":{\"query_cert_no\":\"330101199001010011\",\"query_cust_name\":\"张三\",\"report_time\":\"2026-03-24\"}}";
        String resultResponse = resultSuccessResponse(objectMapper, payload, "ERR_SHOULD_IGNORE");

        when(httpUtil.postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeature"),
                org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
                isNull()
        )).thenReturn(initiateSuccessResponse());
        when(httpUtil.postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
                org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
                isNull()
        )).thenReturn(emptyPayloadResponse, emptyPayloadResponse, emptyPayloadResponse, emptyPayloadResponse, resultResponse);

        CreditParseInvokeResponse actual = parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html");

        assertEquals("330101199001010011", actual.getData().getMappingOutputFields()
                .getPayload().getLxHeader().get("query_cert_no"));
        verify(httpUtil, times(5)).postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
                org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
                isNull()
        );
        assertEquals(List.of(2000L, 2000L, 2000L, 2000L), parseClient.getSleepIntervals());
    }

    @Test
    void creditParseClient_shouldRejectInvalidOuterCode() throws Exception {
        HttpUtil httpUtil = mock(HttpUtil.class);
        TestableCreditParseClient parseClient = new TestableCreditParseClient();
        ObjectMapper objectMapper = new ObjectMapper();
        ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
        ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
        ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult");
        ReflectionTestUtils.setField(parseClient, "orgCode", "999000");
        ReflectionTestUtils.setField(parseClient, "runType", "1");
        ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
        ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);

        when(httpUtil.postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeature"),
                org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
                isNull()
        )).thenReturn(initiateSuccessResponse());
        when(httpUtil.postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
                org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
                isNull()
        )).thenReturn("{\"success\":true,\"code\":99999,\"data\":{\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"0\"}}}");

        LsfxApiException exception = assertThrows(LsfxApiException.class,
                () -> parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html"));

        assertTrue(exception.getMessage().contains("平台状态码异常"));
    }

    @Test
    void creditParseClient_shouldRejectFailedResultStatus() throws Exception {
        HttpUtil httpUtil = mock(HttpUtil.class);
        TestableCreditParseClient parseClient = new TestableCreditParseClient();
        ObjectMapper objectMapper = new ObjectMapper();
        ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
        ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
        ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult");
        ReflectionTestUtils.setField(parseClient, "orgCode", "999000");
        ReflectionTestUtils.setField(parseClient, "runType", "1");
        ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
        ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);

        when(httpUtil.postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeature"),
                org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
                isNull()
        )).thenReturn(initiateSuccessResponse());
        when(httpUtil.postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
                org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
                isNull()
        )).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"reasonMessage\":\"解析失败\",\"reasonCode\":500,\"status\":0,\"mappingOutputFields\":{\"message\":\"结果异常\"}}}");

        LsfxApiException exception = assertThrows(LsfxApiException.class,
                () -> parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html"));

        assertTrue(exception.getMessage().contains("解析失败"));
    }

    @Test
    void creditParseClient_shouldRejectFailedInitiateStatus() throws Exception {
        HttpUtil httpUtil = mock(HttpUtil.class);
        TestableCreditParseClient parseClient = new TestableCreditParseClient();
        ObjectMapper objectMapper = new ObjectMapper();
        ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
        ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
        ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult");
        ReflectionTestUtils.setField(parseClient, "orgCode", "999000");
        ReflectionTestUtils.setField(parseClient, "runType", "1");
        ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
        ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);

        when(httpUtil.postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeature"),
                org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
                isNull()
        )).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"mappingOutputFields\":{\"message\":\"文件写入失败\"},\"reasonMessage\":\"文件写入失败\",\"reasonCode\":500,\"status\":0}}");

        LsfxApiException exception = assertThrows(LsfxApiException.class,
                () -> parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html"));

        assertTrue(exception.getMessage().contains("文件写入失败"));
        verify(httpUtil, times(0)).postUrlEncodedFormForString(
                eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
                org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
                isNull()
        );
    }

    private static String initiateSuccessResponse() {
        return "{\"success\":true,\"code\":10000,\"data\":{\"traceId\":\"TRACE-001\","
                + "\"mappingOutputFields\":{\"message\":\"文件写入成功，流水号为: CCDI_CREDIT_TEST\"},"
                + "\"reasonMessage\":\"Running successfully\",\"procCode\":\"999000\","
                + "\"reasonCode\":200,\"status\":1}}";
    }

    private static String resultSuccessResponse(ObjectMapper objectMapper, String payload, String statusCode) throws Exception {
        return "{\"success\":true,\"code\":10000,\"data\":{\"status\":1,\"reasonCode\":200,"
                + "\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"" + statusCode + "\",\"payload\":"
                + objectMapper.writeValueAsString(payload) + "}}}";
    }

    private static class TestableCreditParseClient extends CreditParseClient {
        private final List<Long> sleepIntervals = new ArrayList<>();

        @Override
        protected void sleepBeforeNextResultQuery(long intervalMillis) {
            sleepIntervals.add(intervalMillis);
        }

        private List<Long> getSleepIntervals() {
            return sleepIntervals;
        }
    }
}
