package com.ruoyi.framework.web.service;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Collections;
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 com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.LoginPolicyType;
import com.ruoyi.common.exception.user.SmsCodeException;
import com.ruoyi.common.exception.user.SmsCodeRequiredException;
import com.ruoyi.framework.config.properties.LoginProperties;
import com.ruoyi.framework.web.service.sms.SmsGatewayClient;
import com.ruoyi.system.service.ISysLoginPolicyService;
import com.ruoyi.system.service.ISysUserService;

@ExtendWith(MockitoExtension.class)
@DisplayName("LoginSmsService")
class LoginSmsServiceTest
{
    @InjectMocks
    private LoginSmsService loginSmsService;

    @Mock
    private RedisCache redisCache;

    @Mock
    private ISysUserService userService;

    @Mock
    private ISysLoginPolicyService loginPolicyService;

    @Mock
    private LoginProperties loginProperties;

    @Mock
    private LoginProperties.InternetSms internetSms;

    @Mock
    private SmsGatewayClient smsGatewayClient;

    @Mock
    private LoginInforRecorder loginInforRecorder;

    private SysUser user;

    @BeforeEach
    void setUp()
    {
        user = new SysUser(1L);
        user.setUserName("admin");
        user.setPhonenumber("13800001111");
        SysRole role = new SysRole();
        role.setRoleKey("admin");
        user.setRoles(Collections.singletonList(role));
    }

    @Test
    @DisplayName("互联网双因子开启时需要短信码")
    void requiresSmsWhenInternetEnabled()
    {
        when(loginProperties.isSmsTwoFactorActive()).thenReturn(true);
        when(loginProperties.getNetworkZone()).thenReturn("INTERNET");
        when(loginPolicyService.resolvePolicyType(eq("INTERNET"), any(), any()))
                .thenReturn(LoginPolicyType.SMS_2FA);

        assertTrue(loginSmsService.requiresSmsTwoFactor(user));
    }

    @Test
    @DisplayName("内网或未启用时不强制短信")
    void noSmsWhenDisabled()
    {
        when(loginProperties.isSmsTwoFactorActive()).thenReturn(false);

        assertFalse(loginSmsService.requiresSmsTwoFactor(user));
        verify(loginPolicyService, never()).resolvePolicyType(anyString(), any(), anyString());
    }

    @Test
    @DisplayName("缺少短信码时拒绝")
    void rejectMissingSmsCode()
    {
        when(loginProperties.isSmsTwoFactorActive()).thenReturn(true);
        when(loginProperties.getNetworkZone()).thenReturn("INTERNET");
        when(loginPolicyService.resolvePolicyType(eq("INTERNET"), any(), any()))
                .thenReturn(LoginPolicyType.SMS_2FA);

        assertThrows(SmsCodeRequiredException.class, () -> loginSmsService.validateLoginSmsCode(user, ""));
    }

    @Test
    @DisplayName("错误短信码时拒绝")
    void rejectWrongSmsCode()
    {
        when(loginProperties.isSmsTwoFactorActive()).thenReturn(true);
        when(loginProperties.getNetworkZone()).thenReturn("INTERNET");
        when(loginPolicyService.resolvePolicyType(eq("INTERNET"), any(), any()))
                .thenReturn(LoginPolicyType.SMS_2FA);
        when(redisCache.getCacheObject("login:sms:13800001111")).thenReturn("123456");

        assertThrows(SmsCodeException.class, () -> loginSmsService.validateLoginSmsCode(user, "000000"));
    }

    @Test
    @DisplayName("正确短信码一次性消费")
    void acceptValidSmsCode()
    {
        when(loginProperties.isSmsTwoFactorActive()).thenReturn(true);
        when(loginProperties.getNetworkZone()).thenReturn("INTERNET");
        when(loginPolicyService.resolvePolicyType(eq("INTERNET"), any(), any()))
                .thenReturn(LoginPolicyType.SMS_2FA);
        when(redisCache.getCacheObject("login:sms:13800001111")).thenReturn("123456");

        loginSmsService.validateLoginSmsCode(user, "123456");
        verify(redisCache).deleteObject("login:sms:13800001111");
    }
}
