package com.zsElectric.boot.thirdParty.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zsElectric.boot.business.mapper.ThirdPartyInfoMapper;
import com.zsElectric.boot.business.model.entity.ThirdPartyInfo;
import com.zsElectric.boot.common.util.AESCryptoUtils;
import com.zsElectric.boot.common.util.HmacMD5Util;
import com.zsElectric.boot.thirdParty.model.*;
import com.zsElectric.boot.thirdParty.service.impl.ThirdPartyTokenServiceImpl;
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.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * ThirdPartyTokenService 测试类
 * 测试 query_token 接口的各种场景
 *
 * @author wzq
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("第三方Token服务测试")
class ThirdPartyTokenServiceTest {

    @Mock
    private ThirdPartyInfoMapper thirdPartyInfoMapper;

    @Mock
    private RedisTemplate<String, Object> redisTemplate;

    @Mock
    private ValueOperations<String, Object> valueOperations;

    @InjectMocks
    private ThirdPartyTokenServiceImpl thirdPartyTokenService;

    private final ObjectMapper objectMapper = new ObjectMapper();

    // 测试用的固定配置（16位字符）
    private static final String TEST_OPERATOR_ID = "12345qwer";
    private static final String TEST_OPERATOR_SECRET = "vfkh4k740lfg88kq";
    private static final String TEST_DATA_SECRET = "bbkwy062pzyjhqmg";  // 16位
    private static final String TEST_DATA_SECRET_IV = "xgbzfgwz6ki2gm5j";  // 16位
    private static final String TEST_SIG_SECRET = "8h9sf4zd5cbtlu8x";

    private ThirdPartyInfo mockThirdPartyInfo;

    @BeforeEach
    void setUp() {
        // 初始化模拟的第三方配置信息
        mockThirdPartyInfo = new ThirdPartyInfo();
        mockThirdPartyInfo.setId(1L);
        mockThirdPartyInfo.setOperatorId(TEST_OPERATOR_ID);
        mockThirdPartyInfo.setOperatorSecret(TEST_OPERATOR_SECRET);
        mockThirdPartyInfo.setDataSecret(TEST_DATA_SECRET);
        mockThirdPartyInfo.setDataSecretIV(TEST_DATA_SECRET_IV);
        mockThirdPartyInfo.setSigSecret(TEST_SIG_SECRET);
        mockThirdPartyInfo.setStatus(0);
    }

    // ==================== 参数校验测试 ====================

    @Test
    @DisplayName("请求参数为null应返回4003错误")
    void testQueryToken_NullRequest() {
        ThirdPartyResponse response = thirdPartyTokenService.queryToken(null);

        assertEquals(4003, response.getRet());
        assertEquals("参数不合法,缺少必需的参数", response.getMsg());
    }

    @Test
    @DisplayName("缺少operatorId应返回4003错误")
    void testQueryToken_MissingOperatorId() {
        ThirdPartyRequest request = new ThirdPartyRequest();
        request.setData("testData");
        request.setTimeStamp("20260312120000");
        request.setSeq("0001");
        request.setSig("testSig");

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        assertEquals(4003, response.getRet());
        assertEquals("参数不合法,缺少必需的参数", response.getMsg());
    }

    @Test
    @DisplayName("缺少data应返回4003错误")
    void testQueryToken_MissingData() {
        ThirdPartyRequest request = new ThirdPartyRequest();
        request.setOperatorId(TEST_OPERATOR_ID);
        request.setTimeStamp("20260312120000");
        request.setSeq("0001");
        request.setSig("testSig");

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        assertEquals(4003, response.getRet());
        assertEquals("参数不合法,缺少必需的参数", response.getMsg());
    }

    @Test
    @DisplayName("缺少timeStamp应返回4003错误")
    void testQueryToken_MissingTimeStamp() {
        ThirdPartyRequest request = new ThirdPartyRequest();
        request.setOperatorId(TEST_OPERATOR_ID);
        request.setData("testData");
        request.setSeq("0001");
        request.setSig("testSig");

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        assertEquals(4003, response.getRet());
        assertEquals("参数不合法,缺少必需的参数", response.getMsg());
    }

    @Test
    @DisplayName("缺少seq应返回4003错误")
    void testQueryToken_MissingSeq() {
        ThirdPartyRequest request = new ThirdPartyRequest();
        request.setOperatorId(TEST_OPERATOR_ID);
        request.setData("testData");
        request.setTimeStamp("20260312120000");
        request.setSig("testSig");

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        assertEquals(4003, response.getRet());
        assertEquals("参数不合法,缺少必需的参数", response.getMsg());
    }

    @Test
    @DisplayName("缺少sig应返回4003错误")
    void testQueryToken_MissingSig() {
        ThirdPartyRequest request = new ThirdPartyRequest();
        request.setOperatorId(TEST_OPERATOR_ID);
        request.setData("testData");
        request.setTimeStamp("20260312120000");
        request.setSeq("0001");

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        assertEquals(4003, response.getRet());
        assertEquals("参数不合法,缺少必需的参数", response.getMsg());
    }

    // ==================== 运营商校验测试 ====================

    @Test
    @DisplayName("运营商不存在应返回4004错误")
    void testQueryToken_OperatorNotFound() {
        ThirdPartyRequest request = buildCompleteRequest("NOTEXIST1", TEST_OPERATOR_SECRET);

        when(thirdPartyInfoMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(null);

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        assertEquals(4004, response.getRet());
        assertEquals("运营商不存在", response.getMsg());
    }

    // ==================== 签名校验测试 ====================

    @Test
    @DisplayName("签名错误应返回4001错误")
    void testQueryToken_InvalidSignature() throws Exception {
        // 构建请求但使用错误的签名
        QueryTokenRequestData tokenData = new QueryTokenRequestData();
        tokenData.setOperatorId(TEST_OPERATOR_ID);
        tokenData.setOperatorSecret(TEST_OPERATOR_SECRET);
        String jsonData = objectMapper.writeValueAsString(tokenData);
        String encryptedData = AESCryptoUtils.encrypt(jsonData, TEST_DATA_SECRET, TEST_DATA_SECRET_IV);

        ThirdPartyRequest request = new ThirdPartyRequest();
        request.setOperatorId(TEST_OPERATOR_ID);
        request.setData(encryptedData);
        request.setTimeStamp("20260312120000");
        request.setSeq("0001");
        request.setSig("WRONG_SIGNATURE_12345678901234567890");  // 错误的签名

        when(thirdPartyInfoMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(mockThirdPartyInfo);

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        assertEquals(4001, response.getRet());
        assertEquals("签名错误", response.getMsg());
        assertNotNull(response.getSig());  // 应该有签名
    }

    // ==================== 业务参数校验测试 ====================

    @Test
    @DisplayName("业务参数中operatorId为空应返回4004错误")
    void testQueryToken_EmptyOperatorIdInData() throws Exception {
        // 构建请求，业务参数中operatorId为空
        QueryTokenRequestData tokenData = new QueryTokenRequestData();
        tokenData.setOperatorId("");  // 空的operatorId
        tokenData.setOperatorSecret(TEST_OPERATOR_SECRET);

        ThirdPartyRequest request = buildRequestWithTokenData(tokenData);
        when(thirdPartyInfoMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(mockThirdPartyInfo);

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        assertEquals(4004, response.getRet());
        assertEquals("请求的业务参数不合法", response.getMsg());
    }

    @Test
    @DisplayName("业务参数中operatorSecret为空应返回4004错误")
    void testQueryToken_EmptyOperatorSecretInData() throws Exception {
        // 构建请求，业务参数中operatorSecret为空
        QueryTokenRequestData tokenData = new QueryTokenRequestData();
        tokenData.setOperatorId(TEST_OPERATOR_ID);
        tokenData.setOperatorSecret("");  // 空的operatorSecret

        ThirdPartyRequest request = buildRequestWithTokenData(tokenData);
        when(thirdPartyInfoMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(mockThirdPartyInfo);

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        assertEquals(4004, response.getRet());
        assertEquals("请求的业务参数不合法", response.getMsg());
    }

    // ==================== 凭证验证测试 ====================

    @Test
    @DisplayName("operatorId不匹配应返回业务失败(resultStatus=1)")
    void testQueryToken_OperatorIdMismatch() throws Exception {
        // 构建请求，业务参数中operatorId不匹配
        QueryTokenRequestData tokenData = new QueryTokenRequestData();
        tokenData.setOperatorId("DIFFERENT1");  // 与数据库中不同的operatorId
        tokenData.setOperatorSecret(TEST_OPERATOR_SECRET);

        ThirdPartyRequest request = buildRequestWithTokenData(tokenData);
        when(thirdPartyInfoMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(mockThirdPartyInfo);
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        // 返回成功响应，但业务数据中resultStatus=1
        assertEquals(0, response.getRet());
        assertNotNull(response.getData());
        assertNotNull(response.getSig());

        // 解密响应数据验证resultStatus
        String decryptedData = AESCryptoUtils.decrypt(response.getData(), TEST_DATA_SECRET, TEST_DATA_SECRET_IV);
        QueryTokenResponseData responseData = objectMapper.readValue(decryptedData, QueryTokenResponseData.class);
        assertEquals(1, responseData.getResultStatus());
        assertEquals(1, responseData.getFailReason());
    }

    @Test
    @DisplayName("operatorSecret不匹配应返回业务失败(resultStatus=1)")
    void testQueryToken_OperatorSecretMismatch() throws Exception {
        // 构建请求，业务参数中operatorSecret不匹配
        QueryTokenRequestData tokenData = new QueryTokenRequestData();
        tokenData.setOperatorId(TEST_OPERATOR_ID);
        tokenData.setOperatorSecret("WrongSecret12345");  // 错误的密钥

        ThirdPartyRequest request = buildRequestWithTokenData(tokenData);
        when(thirdPartyInfoMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(mockThirdPartyInfo);
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        // 返回成功响应，但业务数据中resultStatus=1
        assertEquals(0, response.getRet());

        // 解密响应数据验证resultStatus
        String decryptedData = AESCryptoUtils.decrypt(response.getData(), TEST_DATA_SECRET, TEST_DATA_SECRET_IV);
        QueryTokenResponseData responseData = objectMapper.readValue(decryptedData, QueryTokenResponseData.class);
        assertEquals(1, responseData.getResultStatus());
        assertEquals(1, responseData.getFailReason());
    }

    // ==================== 成功场景测试 ====================

    @Test
    @DisplayName("正常请求应成功获取Token")
    void testQueryToken_Success() throws Exception {
        ThirdPartyRequest request = buildCompleteRequest(TEST_OPERATOR_ID, TEST_OPERATOR_SECRET);

        when(thirdPartyInfoMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(mockThirdPartyInfo);
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        when(valueOperations.get(anyString())).thenReturn(null);  // 无已有Token
        when(redisTemplate.getExpire(anyString(), eq(TimeUnit.SECONDS))).thenReturn(7200L);

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        // 验证响应
        assertEquals(0, response.getRet());
        assertEquals("", response.getMsg());
        assertNotNull(response.getData());
        assertNotNull(response.getSig());

        // 解密响应数据
        String decryptedData = AESCryptoUtils.decrypt(response.getData(), TEST_DATA_SECRET, TEST_DATA_SECRET_IV);
        QueryTokenResponseData responseData = objectMapper.readValue(decryptedData, QueryTokenResponseData.class);

        assertEquals(TEST_OPERATOR_ID, responseData.getOperatorId());
        assertEquals(0, responseData.getResultStatus());
        assertNotNull(responseData.getAccessToken());
        assertTrue(responseData.getAccessToken().length() > 0);
        assertEquals(0, responseData.getFailReason());

        // 验证Redis操作
        verify(valueOperations, times(2)).set(anyString(), any(), eq(7200L), eq(TimeUnit.SECONDS));
    }

    @Test
    @DisplayName("已存在有效Token应复用")
    void testQueryToken_ReuseExistingToken() throws Exception {
        String existingToken = "existingToken12345678901234567890123456789012345678901234567890";

        ThirdPartyRequest request = buildCompleteRequest(TEST_OPERATOR_ID, TEST_OPERATOR_SECRET);

        when(thirdPartyInfoMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(mockThirdPartyInfo);
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        when(valueOperations.get("third_party_access:operator:" + TEST_OPERATOR_ID)).thenReturn(existingToken);
        when(redisTemplate.hasKey("third_party_access:token:" + existingToken)).thenReturn(true);
        when(redisTemplate.getExpire("third_party_access:token:" + existingToken, TimeUnit.SECONDS)).thenReturn(3600L);

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        // 验证响应
        assertEquals(0, response.getRet());

        // 解密响应数据
        String decryptedData = AESCryptoUtils.decrypt(response.getData(), TEST_DATA_SECRET, TEST_DATA_SECRET_IV);
        QueryTokenResponseData responseData = objectMapper.readValue(decryptedData, QueryTokenResponseData.class);

        assertEquals(existingToken, responseData.getAccessToken());
        assertEquals(3600, responseData.getTokenExpirationTime());

        // 验证没有创建新Token
        verify(valueOperations, never()).set(anyString(), any(), anyLong(), any(TimeUnit.class));
    }

    @Test
    @DisplayName("已存在Token但已过期应生成新Token")
    void testQueryToken_ExpiredTokenGenerateNew() throws Exception {
        String expiredToken = "expiredToken12345678901234567890123456789012345678901234567890";

        ThirdPartyRequest request = buildCompleteRequest(TEST_OPERATOR_ID, TEST_OPERATOR_SECRET);

        when(thirdPartyInfoMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(mockThirdPartyInfo);
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        when(valueOperations.get("third_party_access:operator:" + TEST_OPERATOR_ID)).thenReturn(expiredToken);
        when(redisTemplate.hasKey("third_party_access:token:" + expiredToken)).thenReturn(false);  // Token已过期
        when(redisTemplate.getExpire(anyString(), eq(TimeUnit.SECONDS))).thenReturn(7200L);

        ThirdPartyResponse response = thirdPartyTokenService.queryToken(request);

        // 验证响应
        assertEquals(0, response.getRet());

        // 解密响应数据
        String decryptedData = AESCryptoUtils.decrypt(response.getData(), TEST_DATA_SECRET, TEST_DATA_SECRET_IV);
        QueryTokenResponseData responseData = objectMapper.readValue(decryptedData, QueryTokenResponseData.class);

        // 应该是新生成的Token，不是过期的Token
        assertNotEquals(expiredToken, responseData.getAccessToken());

        // 验证创建了新Token
        verify(valueOperations, times(2)).set(anyString(), any(), eq(7200L), eq(TimeUnit.SECONDS));
    }

    // ==================== 辅助方法 ====================

    /**
     * 构建完整的请求（包含正确的签名）
     */
    private ThirdPartyRequest buildCompleteRequest(String operatorId, String operatorSecret) {
        try {
            // 构建业务数据
            QueryTokenRequestData tokenData = new QueryTokenRequestData();
            tokenData.setOperatorId(operatorId);
            tokenData.setOperatorSecret(operatorSecret);

            return buildRequestWithTokenData(tokenData);
        } catch (Exception e) {
            throw new RuntimeException("构建请求失败", e);
        }
    }

    /**
     * 使用指定的TokenData构建请求
     */
    private ThirdPartyRequest buildRequestWithTokenData(QueryTokenRequestData tokenData) throws Exception {
        String jsonData = objectMapper.writeValueAsString(tokenData);
        String encryptedData = AESCryptoUtils.encrypt(jsonData, TEST_DATA_SECRET, TEST_DATA_SECRET_IV);
        String timeStamp = "20260312120000";
        String seq = "0001";

        // 生成正确的签名
        String signContent = TEST_OPERATOR_ID + encryptedData + timeStamp + seq;
        String sig = HmacMD5Util.hmacMD5Hex(signContent, TEST_SIG_SECRET);

        ThirdPartyRequest request = new ThirdPartyRequest();
        request.setOperatorId(TEST_OPERATOR_ID);
        request.setData(encryptedData);
        request.setTimeStamp(timeStamp);
        request.setSeq(seq);
        request.setSig(sig);

        return request;
    }
}
