package cn.novalon.gym.manage.gateway.service.impl;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpMethod;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.*;

/**
 * SignatureServiceImpl单元测试
 * 
 * 文件定义：测试签名服务的核心功能
 * 涉及业务：签名生成、签名验证、时间戳验证、nonce防重放
 * 
 * @author 张翔
 * @date 2026-03-26
 */
@ExtendWith(MockitoExtension.class)
class SignatureServiceImplTest {

    @InjectMocks
    private SignatureServiceImpl signatureService;

    private static final String TEST_SECRET = "TestSecretKey123";
    private static final String TEST_METHOD = "GET";
    private static final String TEST_PATH = "/api/users";
    private static final String TEST_QUERY = "page=1&size=10";
    private static final String TEST_BODY = "";
    private static final String TEST_NONCE = "test-nonce-12345";

    @BeforeEach
    void setUp() {
        ReflectionTestUtils.setField(signatureService, "signatureEnabled", true);
        ReflectionTestUtils.setField(signatureService, "maxAgeMinutes", 5);
        ReflectionTestUtils.setField(signatureService, "nonceCacheSize", 10000);
    }

    @Test
    void testGenerateSignature_ShouldGenerateValidSignature() {
        long timestamp = System.currentTimeMillis();

        String signature = signatureService.generateSignature(
                TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);

        assertNotNull(signature);
        assertFalse(signature.isEmpty());
        assertTrue(signature.length() > 0);
    }

    @Test
    void testGenerateSignature_ShouldGenerateSameSignatureForSameInput() {
        long timestamp = System.currentTimeMillis();

        String signature1 = signatureService.generateSignature(
                TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);
        String signature2 = signatureService.generateSignature(
                TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);

        assertEquals(signature1, signature2);
    }

    @Test
    void testGenerateSignature_ShouldGenerateDifferentSignatureForDifferentInput() {
        long timestamp = System.currentTimeMillis();

        String signature1 = signatureService.generateSignature(
                TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);
        String signature2 = signatureService.generateSignature(
                "POST", TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);

        assertNotEquals(signature1, signature2);
    }

    @Test
    void testVerifySignature_WithValidSignature_ShouldReturnTrue() {
        long timestamp = System.currentTimeMillis();
        String signature = signatureService.generateSignature(
                TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);

        MockServerHttpRequest request = MockServerHttpRequest
                .method(HttpMethod.GET, TEST_PATH + "?" + TEST_QUERY)
                .header("X-Signature", signature)
                .header("X-Timestamp", String.valueOf(timestamp))
                .header("X-Nonce", TEST_NONCE)
                .build();

        boolean isValid = signatureService.verifySignature(request, TEST_SECRET);

        assertTrue(isValid);
    }

    @Test
    void testVerifySignature_WithInvalidSignature_ShouldReturnFalse() {
        long timestamp = System.currentTimeMillis();

        MockServerHttpRequest request = MockServerHttpRequest
                .method(HttpMethod.GET, TEST_PATH)
                .header("X-Signature", "invalid-signature")
                .header("X-Timestamp", String.valueOf(timestamp))
                .header("X-Nonce", TEST_NONCE)
                .build();

        boolean isValid = signatureService.verifySignature(request, TEST_SECRET);

        assertFalse(isValid);
    }

    @Test
    void testVerifySignature_WithMissingHeaders_ShouldReturnFalse() {
        MockServerHttpRequest request = MockServerHttpRequest
                .method(HttpMethod.GET, TEST_PATH)
                .build();

        boolean isValid = signatureService.verifySignature(request, TEST_SECRET);

        assertFalse(isValid);
    }

    @Test
    void testVerifySignature_WithExpiredTimestamp_ShouldReturnFalse() {
        long expiredTimestamp = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(10);
        String signature = signatureService.generateSignature(
                TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, expiredTimestamp, TEST_NONCE, TEST_SECRET);

        MockServerHttpRequest request = MockServerHttpRequest
                .method(HttpMethod.GET, TEST_PATH)
                .header("X-Signature", signature)
                .header("X-Timestamp", String.valueOf(expiredTimestamp))
                .header("X-Nonce", TEST_NONCE)
                .build();

        boolean isValid = signatureService.verifySignature(request, TEST_SECRET);

        assertFalse(isValid);
    }

    @Test
    void testVerifySignature_WithUsedNonce_ShouldReturnFalse() {
        long timestamp = System.currentTimeMillis();
        String signature = signatureService.generateSignature(
                TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);

        signatureService.recordNonce(TEST_NONCE);

        MockServerHttpRequest request = MockServerHttpRequest
                .method(HttpMethod.GET, TEST_PATH)
                .header("X-Signature", signature)
                .header("X-Timestamp", String.valueOf(timestamp))
                .header("X-Nonce", TEST_NONCE)
                .build();

        boolean isValid = signatureService.verifySignature(request, TEST_SECRET);

        assertFalse(isValid);
    }

    @Test
    void testIsTimestampValid_WithValidTimestamp_ShouldReturnTrue() {
        long validTimestamp = System.currentTimeMillis();

        boolean isValid = signatureService.isTimestampValid(validTimestamp, 5);

        assertTrue(isValid);
    }

    @Test
    void testIsTimestampValid_WithExpiredTimestamp_ShouldReturnFalse() {
        long expiredTimestamp = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(10);

        boolean isValid = signatureService.isTimestampValid(expiredTimestamp, 5);

        assertFalse(isValid);
    }

    @Test
    void testIsTimestampValid_WithFutureTimestamp_ShouldReturnFalse() {
        long futureTimestamp = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10);

        boolean isValid = signatureService.isTimestampValid(futureTimestamp, 5);

        assertFalse(isValid);
    }

    @Test
    void testIsNonceUsed_WithNewNonce_ShouldReturnFalse() {
        boolean isUsed = signatureService.isNonceUsed("new-nonce-123");

        assertFalse(isUsed);
    }

    @Test
    void testIsNonceUsed_WithUsedNonce_ShouldReturnTrue() {
        String nonce = "used-nonce-123";
        signatureService.recordNonce(nonce);

        boolean isUsed = signatureService.isNonceUsed(nonce);

        assertTrue(isUsed);
    }

    @Test
    void testRecordNonce_ShouldIncreaseCacheSize() {
        int initialSize = signatureService.getNonceCacheSize();

        signatureService.recordNonce("test-nonce-1");
        signatureService.recordNonce("test-nonce-2");
        signatureService.recordNonce("test-nonce-3");

        int finalSize = signatureService.getNonceCacheSize();
        assertEquals(initialSize + 3, finalSize);
    }

    @Test
    void testCleanupExpiredNonces_ShouldRemoveExpiredEntries() {
        ReflectionTestUtils.setField(signatureService, "nonceCacheSize", 5);

        signatureService.recordNonce("nonce-1");
        signatureService.recordNonce("nonce-2");
        signatureService.recordNonce("nonce-3");
        signatureService.recordNonce("nonce-4");
        signatureService.recordNonce("nonce-5");
        signatureService.recordNonce("nonce-6");

        int cacheSize = signatureService.getNonceCacheSize();
        assertTrue(cacheSize <= 6);
    }

    @Test
    void testVerifySignature_WhenDisabled_ShouldReturnTrue() {
        ReflectionTestUtils.setField(signatureService, "signatureEnabled", false);

        MockServerHttpRequest request = MockServerHttpRequest
                .method(HttpMethod.GET, TEST_PATH)
                .build();

        boolean isValid = signatureService.verifySignature(request, TEST_SECRET);

        assertTrue(isValid);
    }
}
