package tech.easyflow.auth.service.impl;

import cn.hutool.crypto.digest.BCrypt;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.service.SysAccountService;

import java.lang.reflect.Field;
import java.math.BigInteger;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * {@link AuthServiceImpl} 密码修改规则测试。
 */
public class AuthServiceImplPasswordTest {

    private static final BigInteger ACCOUNT_ID = BigInteger.valueOf(10);
    private static final String CURRENT_PASSWORD = "Current!123";
    private static final String NEW_PASSWORD = "Changed!456";

    /**
     * 验证强制重置状态下，Web 会话无需提交当前密码即可更新。
     *
     * @throws Exception 注入测试依赖失败
     */
    @Test
    public void shouldAllowForcedResetWithoutCurrentPasswordForWebSession() throws Exception {
        SysAccountService accountService = mock(SysAccountService.class);
        AuthServiceImpl authService = createService(accountService);
        SysAccount account = buildAccount(true);
        when(accountService.getById(ACCOUNT_ID)).thenReturn(account);
        when(accountService.updateById(any(SysAccount.class))).thenReturn(true);

        authService.updateOwnPassword(
                ACCOUNT_ID,
                null,
                NEW_PASSWORD,
                NEW_PASSWORD,
                AuthLoginSessionPolicy.WEB_DEVICE
        );

        ArgumentCaptor<SysAccount> updateCaptor = ArgumentCaptor.forClass(SysAccount.class);
        verify(accountService).updateById(updateCaptor.capture());
        SysAccount update = updateCaptor.getValue();
        assertTrue(BCrypt.checkpw(NEW_PASSWORD, update.getPassword()));
        assertFalse(update.getPasswordResetRequired());
        assertEquals(ACCOUNT_ID, update.getModifiedBy());
    }

    /**
     * 验证普通改密仍需校验当前密码。
     *
     * @throws Exception 注入测试依赖失败
     */
    @Test
    public void shouldRequireCurrentPasswordForRegularChange() throws Exception {
        SysAccountService accountService = mock(SysAccountService.class);
        AuthServiceImpl authService = createService(accountService);
        when(accountService.getById(ACCOUNT_ID)).thenReturn(buildAccount(false));

        BusinessException exception = expectBusinessException(() -> authService.updateOwnPassword(
                ACCOUNT_ID,
                "Wrong!123",
                NEW_PASSWORD,
                NEW_PASSWORD,
                AuthLoginSessionPolicy.WEB_DEVICE
        ));

        assertEquals(400, exception.getHttpStatus());
        assertEquals("密码不正确", exception.getMessage());
        verify(accountService, never()).updateById(any(SysAccount.class));
    }

    /**
     * 验证 API Key 会话不能使用免当前密码的强制重置流程。
     *
     * @throws Exception 注入测试依赖失败
     */
    @Test
    public void shouldRejectApiKeyForcedReset() throws Exception {
        SysAccountService accountService = mock(SysAccountService.class);
        AuthServiceImpl authService = createService(accountService);
        when(accountService.getById(ACCOUNT_ID)).thenReturn(buildAccount(true));

        BusinessException exception = expectBusinessException(() -> authService.updateOwnPassword(
                ACCOUNT_ID,
                null,
                NEW_PASSWORD,
                NEW_PASSWORD,
                AuthLoginSessionPolicy.API_KEY_DEVICE
        ));

        assertEquals(403, exception.getHttpStatus());
        assertEquals("请使用账号密码登录后重置密码", exception.getMessage());
        verify(accountService, never()).updateById(any(SysAccount.class));
    }

    /**
     * 验证普通改密在当前密码正确时仍可成功更新。
     *
     * @throws Exception 注入测试依赖失败
     */
    @Test
    public void shouldUpdateRegularPasswordWithCorrectCurrentPassword() throws Exception {
        SysAccountService accountService = mock(SysAccountService.class);
        AuthServiceImpl authService = createService(accountService);
        when(accountService.getById(ACCOUNT_ID)).thenReturn(buildAccount(false));
        when(accountService.updateById(any(SysAccount.class))).thenReturn(true);

        authService.updateOwnPassword(
                ACCOUNT_ID,
                CURRENT_PASSWORD,
                NEW_PASSWORD,
                NEW_PASSWORD,
                AuthLoginSessionPolicy.WEB_DEVICE
        );

        verify(accountService).updateById(any(SysAccount.class));
    }

    /**
     * 构造测试账号。
     *
     * @param passwordResetRequired 是否要求重置密码
     * @return 测试账号
     */
    private SysAccount buildAccount(boolean passwordResetRequired) {
        SysAccount account = new SysAccount();
        account.setId(ACCOUNT_ID);
        account.setPassword(BCrypt.hashpw(CURRENT_PASSWORD));
        account.setPasswordResetRequired(passwordResetRequired);
        return account;
    }

    /**
     * 创建并注入账号服务。
     *
     * @param accountService 账号服务
     * @return 认证服务
     * @throws Exception 注入测试依赖失败
     */
    private AuthServiceImpl createService(SysAccountService accountService) throws Exception {
        AuthServiceImpl authService = new AuthServiceImpl();
        Field field = AuthServiceImpl.class.getDeclaredField("sysAccountService");
        field.setAccessible(true);
        field.set(authService, accountService);
        return authService;
    }

    /**
     * 执行操作并返回业务异常。
     *
     * @param action 待执行操作
     * @return 捕获的业务异常
     */
    private BusinessException expectBusinessException(Runnable action) {
        try {
            action.run();
            fail("预期抛出 BusinessException");
            return null;
        } catch (BusinessException exception) {
            return exception;
        }
    }
}
