package com.cf.imes.module.system.service.auth;

import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.service.CaptchaService;
import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.enums.UserTypeEnum;
import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.framework.ip.core.service.IPQueryService;
import com.cf.imes.framework.ip.core.service.dto.IPQueryDataRespDTO;
import com.cf.imes.framework.organ.core.service.OrganFrameworkService;
import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.service.SecurityFrameworkService;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.framework.test.core.ut.BaseMockitoUnitTest;
import com.cf.imes.module.system.api.sms.SmsCodeApi;
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO;
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginSmsCheckReqVO;
import com.cf.imes.module.system.controller.admin.auth.vo.AuthSmsSendReqVO;
import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
import com.cf.imes.module.system.enums.logger.LoginLogTypeEnum;
import com.cf.imes.module.system.enums.logger.LoginResultEnum;
import com.cf.imes.module.system.framework.chenfeng.config.ChenfengProperties;
import com.cf.imes.module.system.framework.jwt.config.JwtProperties;
import com.cf.imes.module.system.framework.product.ProductProperties;
import com.cf.imes.module.system.service.dept.DeptService;
import com.cf.imes.module.system.service.logger.LoginLogService;
import com.cf.imes.module.system.service.oauth2.OAuth2TokenService;
import com.cf.imes.module.system.service.organ.OrganService;
import com.cf.imes.module.system.service.sms.SmsCodeService;
import com.cf.imes.module.system.service.user.AdminUserService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.validation.Validator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import org.mockito.MockedStatic;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionTemplate;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
import java.util.function.Consumer;

import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceException;
import static com.cf.imes.framework.test.core.util.RandomUtils.randomPojo;
import static com.cf.imes.framework.test.core.util.RandomUtils.randomString;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

class AdminAuthServiceImplTest extends BaseMockitoUnitTest {

    @InjectMocks
    private AdminAuthServiceImpl authService;

    @Mock
    private AdminUserService userService;

    @Mock
    private CaptchaService captchaService;

    @Mock
    private LoginLogService loginLogService;

    @Mock
    private SmsCodeApi smsCodeApi;

    @Mock
    private OAuth2TokenService oauth2TokenService;

    @Mock
    private OrganService organService;

    @Mock
    private OrganFrameworkService organFrameworkService;

    @Mock
    private SmsCodeService smsCodeService;

    @Mock
    private IPQueryService ipQueryService;

    @Mock
    private TransactionTemplate transactionTemplate;

    @Mock
    private SecurityFrameworkService securityFrameworkService;

    @Mock
    private DeptService deptService;

    @Mock
    private ProductProperties productProperties;

    @Mock
    private JwtProperties jwtProperties;

    @Mock
    private ChenfengProperties chenfengProperties;

    @Mock
    private Validator validator;

    private AuthLoginReqVO reqVO;

    @BeforeEach
    void setUp() {
        reqVO = new AuthLoginReqVO();
        reqVO.setUsername("admin");
        reqVO.setPassword("123456");
    }

    private AdminUserDO buildUser(){
        AdminUserDO user = new AdminUserDO();
        user.setId(1L);
        user.setUsername("admin");
        user.setPassword("encodedPwd");
        user.setOrganId(10L);
        user.setDeptId(100L);
        user.setStatus(CommonStatusEnum.ENABLE.getStatus());
        return user;
    }

    private AuthLoginReqVO buildLoginReqVO() {
        AuthLoginReqVO reqVO = new AuthLoginReqVO();
        reqVO.setUsername("admin");
        reqVO.setPassword("123456");
        return reqVO;
    }

    @Test
    void authenticate_userNotExists_throwBadCredentials() {
        // mock
        when(userService.getUserUniqueByUserName("admin"))
                .thenReturn(null);

        assertServiceException(() -> authService.authenticate(reqVO),
                AUTH_LOGIN_BAD_CREDENTIALS);


        verify(userService).getUserUniqueByUserName("admin");
    }

    @Test
    void authenticate_passwordNotMatch_throwBadCredentials() {
        AdminUserDO user = buildUser();

        when(userService.getUserUniqueByUserName("admin"))
                .thenReturn(user);
        when(userService.isPasswordMatch("123456", "encodedPwd"))
                .thenReturn(false);

        assertServiceException(() -> authService.authenticate(reqVO),
                AUTH_LOGIN_BAD_CREDENTIALS);
    }

    @Test
    void authenticate_userDisabled_throwUserDisabled() {
        AdminUserDO user = buildUser();
        user.setStatus(CommonStatusEnum.DISABLE.getStatus());

        when(userService.getUserUniqueByUserName("admin"))
                .thenReturn(user);
        when(userService.isPasswordMatch("123456", "encodedPwd"))
                .thenReturn(true);

        assertServiceException(() -> authService.authenticate(reqVO),
                AUTH_LOGIN_USER_DISABLED);
    }

    @Test
    void authenticate_success_returnUser() {
        AdminUserDO user = buildUser();

        when(userService.getUserUniqueByUserName("admin"))
                .thenReturn(user);
        when(userService.isPasswordMatch("123456", "encodedPwd"))
                .thenReturn(true);

        AdminUserDO result = authService.authenticate(reqVO);

        assertSame(user, result);

        verify(organFrameworkService).validOrgan(10L);
        verify(deptService).validUserLoginDept(100L);
    }

    @Test
    void validateCaptcha_disabled_returnDirectly() {
        ChenfengProperties.Captcha captcha = new ChenfengProperties.Captcha();
        captcha.setEnable(false);
        when(chenfengProperties.getCaptcha()).thenReturn(captcha);

        authService.validateCaptcha(reqVO);

        verifyNoInteractions(captchaService);
    }

    @Test
    void validateCaptcha_success() {
        when(chenfengProperties.getCaptcha()).thenReturn(new ChenfengProperties.Captcha());

        ResponseModel response = mock(ResponseModel.class);
        when(response.isSuccess()).thenReturn(true);
        when(captchaService.verification(any())).thenReturn(response);

        authService.validateCaptcha(reqVO);

        verify(captchaService).verification(any());
    }

    @Test
    void validateCaptcha_fail_throwException() {
        when(chenfengProperties.getCaptcha()).thenReturn(new ChenfengProperties.Captcha());
        when(validator.validate(any(), any()))
                .thenReturn(Collections.emptySet());

        ResponseModel response = mock(ResponseModel.class);
        when(response.isSuccess()).thenReturn(false);
        when(response.getRepMsg()).thenReturn("captcha error");
        when(captchaService.verification(any())).thenReturn(response);

        assertServiceException(() -> authService.validateCaptcha(reqVO),
                AUTH_LOGIN_CAPTCHA_CODE_ERROR);
    }

    @Test
    void login_auth_manageendpoint_permission_error() {
        ChenfengProperties.Captcha captcha = new ChenfengProperties.Captcha();
        captcha.setEnable(false);
        when(chenfengProperties.getCaptcha()).thenReturn(captcha);

        // authenticate success
        AdminUserDO user = buildUser();

        when(userService.getUserUniqueByUserName("admin"))
                .thenReturn(user);
        when(userService.isPasswordMatch("123456", "encodedPwd"))
                .thenReturn(true);

        assertServiceException(() -> authService.login(buildLoginReqVO(), true),
                AUTH_MANAGEENDPOINT_LOGIN_PERMISSION_ERROR);
    }

    @Test
    void login_auth_org_data_code_not_exists() {
        ChenfengProperties.Captcha captcha = new ChenfengProperties.Captcha();
        captcha.setEnable(false);
        when(chenfengProperties.getCaptcha()).thenReturn(captcha);

        // authenticate success
        AdminUserDO user = buildUser();

        when(userService.getUserUniqueByUserName("admin"))
                .thenReturn(user);
        when(userService.isPasswordMatch("123456", "encodedPwd"))
                .thenReturn(true);
        when(securityFrameworkService.hasAnyRoles(anyLong(), anyString())).thenReturn(true);

        OrganizationDO organ = new OrganizationDO();
        organ.setDataSourceCode(null);
        when(organService.getOrgan(anyLong())).thenReturn(organ);

        assertServiceException(() -> authService.login(buildLoginReqVO(), false),
                ORGAN_DATA_CODE_NOT_EXISTS);
    }

    @Test
    void login_success(){
        ChenfengProperties.Captcha captcha = new ChenfengProperties.Captcha();
        captcha.setEnable(false);
        ChenfengProperties.Gray gray = new ChenfengProperties.Gray();
        gray.setVersion("2.0.0");

        when(chenfengProperties.getCaptcha()).thenReturn(captcha);
        when(chenfengProperties.getGray()).thenReturn(gray);

        // authenticate success
        AdminUserDO user = buildUser();
        user.setOrganId(1L);

        when(userService.getUserUniqueByUserName("admin"))
                .thenReturn(user);
        when(userService.isPasswordMatch("123456", "encodedPwd"))
                .thenReturn(true);

        OrganizationDO organ = new OrganizationDO();
        organ.setDataSourceCode("imes_prod");
        organ.setGrayStatus(CommonStatusEnum.ENABLE.getStatus());
        when(organService.getOrgan(anyLong())).thenReturn(organ);

        authService.login(buildLoginReqVO(), true);

        assertEquals(gray.getVersion(), user.getGrayVersion());
    }

    @Test
    void login_manageAndDefaultGrayVersion_success(){
        ChenfengProperties.Captcha captcha = new ChenfengProperties.Captcha();
        captcha.setEnable(false);
        ChenfengProperties.Gray gray = new ChenfengProperties.Gray();
        gray.setVersion("2.0.0");

        when(chenfengProperties.getCaptcha()).thenReturn(captcha);
        when(chenfengProperties.getGray()).thenReturn(gray);

        // authenticate success
        AdminUserDO user = buildUser();

        when(userService.getUserUniqueByUserName("admin"))
                .thenReturn(user);
        when(userService.isPasswordMatch("123456", "encodedPwd"))
                .thenReturn(true);

        OrganizationDO organ = new OrganizationDO();
        organ.setDataSourceCode("imes_prod");
        organ.setGrayStatus(CommonStatusEnum.DISABLE.getStatus());
        when(organService.getOrgan(anyLong())).thenReturn(organ);

        authService.login(buildLoginReqVO(), false);

        assertEquals(gray.getDefaultVersion(), user.getGrayDefaultVersion());
    }

    @Test
    void recordLoginLog_exception_shouldRollback() {
        // 1. mock TransactionStatus
        TransactionStatus status = mock(TransactionStatus.class);

        // 2. 让 TransactionTemplate 直接执行回调
        doAnswer(invocation -> {
            Consumer<TransactionStatus> callback = invocation.getArgument(0);
            callback.accept(status);
            return null;
        }).when(transactionTemplate).executeWithoutResult(any());

        // 3. 让 try 内部抛异常
        doThrow(new RuntimeException("db error"))
                .when(loginLogService).createLoginLog(any());

        // 4. 调用方法
        authService.createLoginLog(1L, "测试用户", UserTypeEnum.ADMIN, 1L,
                LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.SUCCESS);

        // 5. 验证回滚被标记
        verify(status).setRollbackOnly();
    }

    @Test
    void recordLoginLog_success() {
        // 1. mock TransactionStatus
        TransactionStatus status = mock(TransactionStatus.class);

        // 2. 让 TransactionTemplate 直接执行回调
        doAnswer(invocation -> {
            Consumer<TransactionStatus> callback = invocation.getArgument(0);
            callback.accept(status);
            return null;
        }).when(transactionTemplate).executeWithoutResult(any());

        // mock 行为
        when(ipQueryService.serviceEnable()).thenReturn(true);

        IPQueryDataRespDTO ipResp = new IPQueryDataRespDTO();
        ipResp.setProv("浙江");
        ipResp.setCity("杭州");
        ipResp.setArea("西湖");
            when(ipQueryService.querySource(nullable(String.class))).thenReturn(ipResp);

        // 4. 调用方法
        authService.createLoginLog(1L, "测试用户", UserTypeEnum.ADMIN, 1L,
                LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.SUCCESS);

        // 验证更新用户登录信息
        verify(userService).updateUserLogin(eq(1L), nullable(String.class));

        // 不应回滚
        verify(status, never()).setRollbackOnly();
    }

    @Test
    void recordLoginLog_errorlog_success() {
        // 1. mock TransactionStatus
        TransactionStatus status = mock(TransactionStatus.class);

        // 2. 让 TransactionTemplate 直接执行回调
        doAnswer(invocation -> {
            Consumer<TransactionStatus> callback = invocation.getArgument(0);
            callback.accept(status);
            return null;
        }).when(transactionTemplate).executeWithoutResult(any());

        // mock 行为
        when(ipQueryService.serviceEnable()).thenReturn(false);

        // 4. 调用方法
        authService.createLoginLog(null, "测试用户", UserTypeEnum.ADMIN, 1L,
                LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.BAD_CREDENTIALS);

        // 验证更新用户登录信息
        verify(userService, never()).updateUserLogin(anyLong(), anyString());
    }

    @Test
    void loginCheck_userNotExist_shouldReturnFalse_andRecordLog() {
        // given
        String mobile = "13800000000";
        when(userService.getUserUniqueByUserName(mobile)).thenReturn(null);

        // when
        boolean result = authService.loginCheck(mobile);

        // then
        assertFalse(result);
    }

    @Test
    void loginCheck_userExist_passwordEmpty_shouldReturnTrue() {
        // given
        String mobile = "13800000000";
        AdminUserDO user = new AdminUserDO();
        user.setPassword(""); // 或 null

        when(userService.getUserUniqueByUserName(mobile)).thenReturn(user);

        // when
        boolean result = authService.loginCheck(mobile);

        // then
        assertTrue(result);
    }

    @Test
    void loginCheck_userExist_passwordNotEmpty_shouldReturnFalse() {
        // given
        String mobile = "13800000000";
        AdminUserDO user = new AdminUserDO();
        user.setPassword("encrypted-password");

        when(userService.getUserUniqueByUserName(mobile)).thenReturn(user);

        // when
        boolean result = authService.loginCheck(mobile);

        // then
        assertFalse(result);
    }

    @Test
    void testLoginSmsCheck_userNotExist() {
        String mobile = "13800000000";
        AuthLoginSmsCheckReqVO reqVO = new AuthLoginSmsCheckReqVO();
        reqVO.setMobile(mobile);

        // 模拟用户不存在
        when(userService.getUserUniqueByUserName(mobile)).thenReturn(null);

        boolean result = authService.loginSmsCheck(reqVO);

        assertFalse(result);
    }

    @Test
    void testLoginSmsCheck_userExistSmsValid() {
        String mobile = "13800000000";
        AuthLoginSmsCheckReqVO reqVO = new AuthLoginSmsCheckReqVO();
        reqVO.setMobile(mobile);

        // 模拟用户存在
        AdminUserDO user = new AdminUserDO();
        when(userService.getUserUniqueByUserName(mobile)).thenReturn(user);

        // 模拟验证码校验成功（不抛异常）
        doNothing().when(smsCodeService).validateSmsCode(reqVO);

        boolean result = authService.loginSmsCheck(reqVO);

        assertTrue(result);

        // 验证 smsCodeService.validateSmsCode 被调用
        verify(smsCodeService, times(1)).validateSmsCode(reqVO);
    }

    @Test
    void testSendSmsCode_mobileNotExist() {
        String mobile = "13800000000";
        AuthSmsSendReqVO reqVO = new AuthSmsSendReqVO();
        reqVO.setMobile(mobile);

        // 模拟用户不存在
        when(userService.getUserByMobile(mobile)).thenReturn(null);

        assertServiceException(() -> authService.sendSmsCode(reqVO),
                AUTH_MOBILE_NOT_EXISTS);

    }

    @Test
    void testSendSmsCode_mobileExists() {
        String mobile = "13800000000";
        AuthSmsSendReqVO reqVO = new AuthSmsSendReqVO();
        reqVO.setMobile(mobile);

        // 模拟用户存在
        when(userService.getUserByMobile(mobile)).thenReturn(new AdminUserDO());

        // 模拟 smsCodeApi 调用成功
        CommonResult<Boolean> mockResult = CommonResult.success(true);
        when(smsCodeApi.sendSmsCode(any())).thenReturn(mockResult);

        authService.sendSmsCode(reqVO);

        // 验证 smsCodeApi 被调用一次
        verify(smsCodeApi, times(1)).sendSmsCode(any());
    }

    @Test
    void testLogout_success() {
        // 准备参数
        String token = randomString();
        // mock
        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
                .setUserType(UserTypeEnum.ADMIN.getValue()));
        when(oauth2TokenService.removeAccessToken(eq(token))).thenReturn(accessTokenDO);

        // 调用
        authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
        // 校验调用参数
        verify(loginLogService).createLoginLog(
                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType())
                        && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))
        );
        // 调用，并校验

    }

    @Test
    void testLogout_fail() {
        // 准备参数
        String token = randomString();

        // 调用
        authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
        // 校验调用参数
        verify(loginLogService, never()).createLoginLog(any());
    }

    @Test
    void recordLogoutLog_success() {

        // mock 行为
        when(ipQueryService.serviceEnable()).thenReturn(true);

        IPQueryDataRespDTO ipResp = new IPQueryDataRespDTO();
        ipResp.setProv("浙江");
        ipResp.setCity("杭州");
        ipResp.setArea("西湖");
        when(ipQueryService.querySource(nullable(String.class))).thenReturn(ipResp);

        // 4. 调用方法
        authService.createLogoutLog(1L, "测试用户", UserTypeEnum.ADMIN.getValue(), LoginLogTypeEnum.LOGOUT_SELF.getType());

        // 验证更新用户登录信息
        verify(loginLogService).createLoginLog(any());
    }

    @Test
    void testGetMesJwtToken() {
        // mock 登录用户
        LoginUser loginUser = new LoginUser();
        loginUser.setId(123L);
        loginUser.setOrganId(456L);

        try (MockedStatic<SecurityFrameworkUtils> utilities = mockStatic(SecurityFrameworkUtils.class)) {
            utilities.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);

            // mock jwtProperties
            when(jwtProperties.getMesTokenTtl()).thenReturn(Duration.ofHours(1));
            when(jwtProperties.getMesSecret()).thenReturn("12345678901234567890123456789012"); // 至少 32 字节
            when(jwtProperties.getMesAud()).thenReturn("test-aud");

            String token = authService.getMesJwtToken();
            assertNotNull(token);

            // 验证 token 内容
            SecretKey secretKey = Keys.hmacShaKeyFor(jwtProperties.getMesSecret().getBytes(StandardCharsets.UTF_8));
            Claims claims = Jwts.parserBuilder()
                    .setSigningKey(secretKey)
                    .build()
                    .parseClaimsJws(token)
                    .getBody();

            assertEquals(123L, claims.get("userId", Long.class));
            assertEquals(456L, claims.get("companyId", Long.class));
            assertEquals("test-aud", claims.getAudience());
            assertNotNull(claims.getExpiration());
            assertNotNull(claims.getNotBefore());
        }
    }

    @Test
    void testGetMesJwtToken_notLogin() {
        assertServiceException(() -> authService.getMesJwtToken(), GlobalErrorCodeConstants.UNAUTHORIZED);
    }
}
