package tech.easyflow.system.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.crypto.digest.BCrypt;
import cn.idev.excel.EasyExcel;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcel;
import cn.idev.excel.context.AnalysisContext;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.read.listener.ReadListener;
import cn.idev.excel.write.metadata.WriteSheet;
import com.mybatisflex.core.query.QueryWrapper;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.MockedStatic;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.multipart.MultipartFile;
import tech.easyflow.common.constant.enums.EnumAccountType;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.config.AccountSecurityProperties;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.entity.SysDept;
import tech.easyflow.system.entity.SysRole;
import tech.easyflow.system.entity.vo.SysAccountImportErrorDetailVo;
import tech.easyflow.system.entity.vo.SysAccountImportResultVo;
import tech.easyflow.system.mapper.SysDeptMapper;
import tech.easyflow.system.mapper.SysPositionMapper;
import tech.easyflow.system.mapper.SysRoleMapper;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

/**
 * {@link SysAccountServiceImpl} 测试。
 */
public class SysAccountServiceImplTest {

    /**
     * 验证展示名解析优先级与缺失数据回退。
     */
    @Test
    public void shouldResolveDisplayNameByNicknameThenLoginNameThenId() {
        SysAccountServiceImpl service = spy(new SysAccountServiceImpl());

        SysAccount nicknameAccount = new SysAccount();
        nicknameAccount.setId(BigInteger.ONE);
        nicknameAccount.setNickname("管理员");
        nicknameAccount.setLoginName("admin");

        SysAccount loginNameAccount = new SysAccount();
        loginNameAccount.setId(BigInteger.valueOf(2));
        loginNameAccount.setNickname("   ");
        loginNameAccount.setLoginName("operator");

        doReturn(List.of(nicknameAccount, loginNameAccount))
                .when(service)
                .list(any(QueryWrapper.class));

        Map<BigInteger, String> displayNameMap = service.resolveDisplayNameMap(
                List.of(BigInteger.ONE, BigInteger.valueOf(2), BigInteger.valueOf(3))
        );

        assertEquals("管理员", displayNameMap.get(BigInteger.ONE));
        assertEquals("operator", displayNameMap.get(BigInteger.valueOf(2)));
        assertEquals("3", displayNameMap.get(BigInteger.valueOf(3)));
        verify(service, times(1)).list(any(QueryWrapper.class));
    }

    /**
     * 验证重置密码返回实际明文，同时持久化对应密文并踢出旧登录态。
     *
     * @throws Exception 注入测试配置失败
     */
    @Test
    public void shouldReturnPlainPasswordAfterReset() throws Exception {
        BigInteger accountId = BigInteger.valueOf(10);
        BigInteger operatorId = BigInteger.valueOf(20);
        String expectedPassword = "!Reset123";
        SysAccountServiceImpl service = spy(new SysAccountServiceImpl());
        AccountSecurityProperties properties = new AccountSecurityProperties();
        properties.setDefaultResetPassword(expectedPassword);
        properties.afterPropertiesSet();
        setField(service, "accountSecurityProperties", properties);

        SysAccount account = new SysAccount();
        account.setId(accountId);
        account.setAccountType(EnumAccountType.NORMAL.getCode());
        doReturn(account).when(service).getById(accountId);
        doReturn(true).when(service).updateById(any(SysAccount.class));

        try (MockedStatic<StpUtil> stp = mockStatic(StpUtil.class)) {
            String actualPassword = service.resetPassword(accountId, operatorId);

            assertEquals(expectedPassword, actualPassword);
            ArgumentCaptor<SysAccount> updateCaptor = ArgumentCaptor.forClass(SysAccount.class);
            verify(service).updateById(updateCaptor.capture());
            SysAccount update = updateCaptor.getValue();
            assertTrue(BCrypt.checkpw(expectedPassword, update.getPassword()));
            assertEquals(Boolean.TRUE, update.getPasswordResetRequired());
            assertEquals(operatorId, update.getModifiedBy());
            stp.verify(() -> StpUtil.kickout(accountId), times(1));
        }
    }

    /**
     * 验证下载模板明确标记角色为必填字段。
     *
     * @throws Exception 注入测试配置失败
     */
    @Test
    public void shouldMarkRoleAsRequiredInImportTemplate() throws Exception {
        SysAccountServiceImpl service = new SysAccountServiceImpl();
        AccountSecurityProperties properties = new AccountSecurityProperties();
        properties.setDefaultResetPassword("Template123!");
        setField(service, "accountSecurityProperties", properties);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        service.writeImportTemplate(outputStream);

        List<String> headers = readFirstSheetHeaders(outputStream.toByteArray());
        assertTrue(headers.contains("部门路径*"));
        assertTrue(headers.contains("角色名称*"));
    }

    /**
     * 验证多个顶级部门分别作为路径起点，且不会把总公司错误拼接到其他顶级分支。
     *
     * @throws Exception 注入测试依赖或构造上传文件失败
     */
    @Test
    public void shouldResolveDepartmentPathFromItsOwnTopLevelBranch() throws Exception {
        SysDept rootCompany = buildDept(1, 0, "总公司");
        SysDept technology = buildDept(2, 0, "技术部");
        SysDept technologyResearch = buildDept(3, 2, "研发");
        SysDept technologyDelivery = buildDept(4, 2, "交付");
        SysDept product = buildDept(5, 0, "产品部");
        SysDept productResearch = buildDept(6, 5, "研发");
        SysAccountServiceImpl service = createReadyImportService(List.of(
                rootCompany,
                technology,
                technologyResearch,
                technologyDelivery,
                product,
                productResearch
        ));

        byte[] workbook = createImportWorkbook(
                importHeaders("部门路径*"),
                List.of(
                        List.of("技术部/研发", "tech-research", "技术研发", "普通员工"),
                        List.of(" 产品部 / 研发 ", "product-research", "产品研发", "普通员工"),
                        List.of("技术部/交付", "delivery", "交付人员", "普通员工"),
                        List.of("总公司", "head-office", "总部人员", "普通员工"),
                        List.of("交付", "delivery-short", "唯一名称兼容", "普通员工")
                )
        );

        SysAccountImportResultVo result = service.importAccounts(
                mockMultipartFile(workbook),
                importLoginAccount()
        );

        assertEquals(5, result.getSuccessCount());
        assertEquals(0, result.getErrorCount());
        ArgumentCaptor<SysAccount> accountCaptor = ArgumentCaptor.forClass(SysAccount.class);
        verify(service, times(5)).save(accountCaptor.capture());
        assertEquals(
                List.of(
                        BigInteger.valueOf(3),
                        BigInteger.valueOf(6),
                        BigInteger.valueOf(4),
                        BigInteger.ONE,
                        BigInteger.valueOf(4)
                ),
                accountCaptor.getAllValues().stream().map(SysAccount::getDeptId).toList()
        );
    }

    /**
     * 验证当前模板优先把单段内容解析为顶级部门完整路径。
     *
     * @throws Exception 注入测试依赖或构造上传文件失败
     */
    @Test
    public void shouldPreferTopLevelPathWhenCurrentTemplateHasDuplicatedDepartmentName() throws Exception {
        SysAccountServiceImpl service = createReadyImportService(List.of(
                buildDept(1, 0, "交易银行部"),
                buildDept(2, 0, "石家庄分行"),
                buildDept(3, 2, "交易银行部"),
                buildDept(4, 0, "济南分行"),
                buildDept(5, 4, "交易银行部")
        ));
        byte[] workbook = createImportWorkbook(
                importHeaders("部门路径*"),
                List.of(List.of(" 交易银行部 ", "head-office-trade", "总行交易银行", "普通员工"))
        );

        SysAccountImportResultVo result = service.importAccounts(
                mockMultipartFile(workbook),
                importLoginAccount()
        );

        assertEquals(1, result.getSuccessCount());
        assertEquals(0, result.getErrorCount());
        ArgumentCaptor<SysAccount> accountCaptor = ArgumentCaptor.forClass(SysAccount.class);
        verify(service).save(accountCaptor.capture());
        assertEquals(BigInteger.ONE, accountCaptor.getValue().getDeptId());
    }

    /**
     * 验证当前模板无法按完整路径命中重名子部门时返回可填写的候选路径。
     *
     * @throws Exception 注入测试依赖或构造上传文件失败
     */
    @Test
    public void shouldListCandidatePathsForDuplicatedDepartmentName() throws Exception {
        SysAccountServiceImpl service = createReadyImportService(List.of(
                buildDept(1, 0, "石家庄分行"),
                buildDept(2, 1, "交易银行部"),
                buildDept(3, 0, "济南分行"),
                buildDept(4, 3, "交易银行部")
        ));
        byte[] workbook = createImportWorkbook(
                importHeaders("部门路径*"),
                List.of(List.of("交易银行部", "ambiguous-trade", "重名交易银行", "普通员工"))
        );

        SysAccountImportResultVo result = service.importAccounts(
                mockMultipartFile(workbook),
                importLoginAccount()
        );

        assertEquals(0, result.getSuccessCount());
        assertEquals(1, result.getErrorCount());
        String reason = result.getErrorRows().get(0).getDetails().stream()
                .filter(detail -> "部门路径".equals(detail.getFieldName()))
                .findFirst()
                .orElseThrow()
                .getReason();
        assertTrue(reason.contains("石家庄分行/交易银行部"));
        assertTrue(reason.contains("济南分行/交易银行部"));
        verify(service, never()).save(any(SysAccount.class));
    }

    /**
     * 验证错误地把并列顶级部门拼到总公司之后时不会发生误匹配。
     *
     * @throws Exception 注入测试依赖或构造上传文件失败
     */
    @Test
    public void shouldRejectPathThatCrossesTopLevelBranches() throws Exception {
        SysAccountServiceImpl service = createReadyImportService(List.of(
                buildDept(1, 0, "总公司"),
                buildDept(2, 0, "技术部"),
                buildDept(3, 2, "研发")
        ));
        byte[] workbook = createImportWorkbook(
                importHeaders("部门路径*"),
                List.of(List.of("总公司/技术部/研发", "wrong-root", "错误根路径", "普通员工"))
        );

        SysAccountImportResultVo result = service.importAccounts(
                mockMultipartFile(workbook),
                importLoginAccount()
        );

        assertEquals(0, result.getSuccessCount());
        assertEquals(1, result.getErrorCount());
        assertTrue(result.getErrorRows().get(0).getDetails().stream().anyMatch(detail ->
                "部门路径".equals(detail.getFieldName())
                        && detail.getReason().contains("部门路径不存在")
        ));
        verify(service, never()).save(any(SysAccount.class));
    }

    /**
     * 验证旧模板中的唯一部门名称继续兼容，重名叶子部门要求填写完整路径。
     *
     * @throws Exception 注入测试依赖或构造上传文件失败
     */
    @Test
    public void shouldKeepLegacyUniqueNameAndRejectDuplicatedName() throws Exception {
        SysAccountServiceImpl service = createReadyImportService(List.of(
                buildDept(1, 0, "技术部"),
                buildDept(2, 1, "研发"),
                buildDept(3, 0, "产品部"),
                buildDept(4, 3, "研发"),
                buildDept(5, 1, "交付")
        ));
        byte[] workbook = createImportWorkbook(
                importHeaders("部门名称*"),
                List.of(
                        List.of("交付", "legacy-unique", "旧模板唯一名称", "普通员工"),
                        List.of("研发", "legacy-duplicate", "旧模板重名名称", "普通员工")
                )
        );

        SysAccountImportResultVo result = service.importAccounts(
                mockMultipartFile(workbook),
                importLoginAccount()
        );

        assertEquals(1, result.getSuccessCount());
        assertEquals(1, result.getErrorCount());
        assertTrue(result.getErrorRows().get(0).getDetails().stream().anyMatch(detail ->
                detail.getReason().contains("请填写从所属分支顶级部门开始的完整路径")
        ));
        ArgumentCaptor<SysAccount> accountCaptor = ArgumentCaptor.forClass(SysAccount.class);
        verify(service).save(accountCaptor.capture());
        assertEquals(BigInteger.valueOf(5), accountCaptor.getValue().getDeptId());
    }

    /**
     * 验证旧部门名称表头不会把重名顶级部门静默解释为完整路径。
     *
     * @throws Exception 注入测试依赖或构造上传文件失败
     */
    @Test
    public void shouldKeepLegacyNameAmbiguityForDuplicatedTopLevelDepartment() throws Exception {
        SysAccountServiceImpl service = createReadyImportService(List.of(
                buildDept(1, 0, "交易银行部"),
                buildDept(2, 0, "石家庄分行"),
                buildDept(3, 2, "交易银行部")
        ));
        byte[] workbook = createImportWorkbook(
                importHeaders("部门名称*"),
                List.of(List.of("交易银行部", "legacy-trade", "旧模板交易银行", "普通员工"))
        );

        SysAccountImportResultVo result = service.importAccounts(
                mockMultipartFile(workbook),
                importLoginAccount()
        );

        assertEquals(0, result.getSuccessCount());
        assertEquals(1, result.getErrorCount());
        assertTrue(result.getErrorRows().get(0).getDetails().stream().anyMatch(detail ->
                detail.getReason().contains("交易银行部")
                        && detail.getReason().contains("石家庄分行/交易银行部")
        ));
        verify(service, never()).save(any(SysAccount.class));
    }

    /**
     * 验证同一父部门下的同名子部门不会被完整路径静默匹配到任意一条记录。
     *
     * @throws Exception 注入测试依赖或构造上传文件失败
     */
    @Test
    public void shouldRejectDuplicatedFullDepartmentPath() throws Exception {
        SysAccountServiceImpl service = createReadyImportService(List.of(
                buildDept(1, 0, "技术部"),
                buildDept(2, 1, "研发"),
                buildDept(3, 1, "研发")
        ));
        byte[] workbook = createImportWorkbook(
                importHeaders("部门路径*"),
                List.of(List.of("技术部/研发", "duplicate-path", "重复路径", "普通员工"))
        );

        SysAccountImportResultVo result = service.importAccounts(
                mockMultipartFile(workbook),
                importLoginAccount()
        );

        assertEquals(0, result.getSuccessCount());
        assertEquals(1, result.getErrorCount());
        assertTrue(result.getErrorRows().get(0).getDetails().stream().anyMatch(detail ->
                detail.getReason().contains("部门完整路径存在重名")
        ));
        verify(service, never()).save(any(SysAccount.class));
    }

    /**
     * 验证空层级和循环部门关系均返回明确错误，且不会产生错误账号。
     *
     * @throws Exception 注入测试依赖或构造上传文件失败
     */
    @Test
    public void shouldRejectMalformedPathAndCyclicDepartmentHierarchy() throws Exception {
        SysAccountServiceImpl service = createReadyImportService(List.of(
                buildDept(1, 2, "技术部"),
                buildDept(2, 1, "研发")
        ));
        byte[] workbook = createImportWorkbook(
                importHeaders("部门路径*"),
                List.of(
                        List.of("技术部//研发", "malformed-path", "空层级", "普通员工"),
                        List.of("技术部/研发", "cyclic-path", "循环层级", "普通员工"),
                        List.of("技术部", "cyclic-name", "名称兼容入口", "普通员工")
                )
        );

        SysAccountImportResultVo result = service.importAccounts(
                mockMultipartFile(workbook),
                importLoginAccount()
        );

        assertEquals(0, result.getSuccessCount());
        assertEquals(3, result.getErrorCount());
        assertTrue(result.getErrorRows().get(0).getDetails().stream().anyMatch(detail ->
                detail.getReason().contains("路径格式不正确")
        ));
        assertTrue(result.getErrorRows().get(1).getDetails().stream().anyMatch(detail ->
                detail.getReason().contains("层级关系异常")
        ));
        assertTrue(result.getErrorRows().get(2).getDetails().stream().anyMatch(detail ->
                detail.getReason().contains("层级关系异常")
        ));
        verify(service, never()).save(any(SysAccount.class));
    }

    /**
     * 验证部门名称包含路径分隔符时拒绝匹配，避免名称与层级路径产生歧义。
     *
     * @throws Exception 注入测试依赖或构造上传文件失败
     */
    @Test
    public void shouldRejectDepartmentNameContainingPathSeparator() throws Exception {
        SysAccountServiceImpl service = createReadyImportService(List.of(
                buildDept(1, 0, "平台/中台")
        ));
        byte[] workbook = createImportWorkbook(
                importHeaders("部门路径*"),
                List.of(List.of("平台/中台", "separator-name", "分隔符名称", "普通员工"))
        );

        SysAccountImportResultVo result = service.importAccounts(
                mockMultipartFile(workbook),
                importLoginAccount()
        );

        assertEquals(0, result.getSuccessCount());
        assertEquals(1, result.getErrorCount());
        assertTrue(result.getErrorRows().get(0).getDetails().stream().anyMatch(detail ->
                detail.getReason().contains("层级关系异常")
        ));
        verify(service, never()).save(any(SysAccount.class));
    }

    /**
     * 验证缺少角色列时直接返回模板表头错误。
     *
     * @throws Exception 构造上传文件失败
     */
    @Test
    public void shouldRejectImportTemplateWithoutRequiredRoleHeader() throws Exception {
        SysAccountServiceImpl service = new SysAccountServiceImpl();
        byte[] workbook = createImportWorkbook(
                List.of(
                        List.of("部门名称*"),
                        List.of("登录账号*"),
                        List.of("昵称*")
                ),
                List.of(List.of("研发部", "missing-role", "缺少角色"))
        );

        BusinessException exception = assertThrows(
                BusinessException.class,
                () -> service.importAccounts(mockMultipartFile(workbook), new LoginAccount())
        );

        assertTrue(exception.getMessage().contains("角色名称*"));
    }

    /**
     * 验证新旧部门表头同时存在时拒绝导入，避免列值来源不明确。
     *
     * @throws Exception 构造上传文件失败
     */
    @Test
    public void shouldRejectImportTemplateWithBothDepartmentHeaders() throws Exception {
        SysAccountServiceImpl service = new SysAccountServiceImpl();
        byte[] workbook = createImportWorkbook(
                List.of(
                        List.of("部门路径*"),
                        List.of("部门名称*"),
                        List.of("登录账号*"),
                        List.of("昵称*"),
                        List.of("角色名称*")
                ),
                List.of(List.of("技术部/研发", "研发", "ambiguous-dept", "歧义部门列", "普通员工"))
        );

        BusinessException exception = assertThrows(
                BusinessException.class,
                () -> service.importAccounts(mockMultipartFile(workbook), new LoginAccount())
        );

        assertTrue(exception.getMessage().contains("只能保留一个"));
    }

    /**
     * 验证重复部门路径表头会被拒绝，避免导入时静默选取错误列。
     *
     * @throws Exception 构造上传文件失败
     */
    @Test
    public void shouldRejectImportTemplateWithDuplicatedDepartmentHeader() throws Exception {
        SysAccountServiceImpl service = new SysAccountServiceImpl();
        byte[] workbook = createImportWorkbook(
                List.of(
                        List.of("部门路径*"),
                        List.of("部门路径*"),
                        List.of("登录账号*"),
                        List.of("昵称*"),
                        List.of("角色名称*")
                ),
                List.of(List.of("技术部/研发", "产品部/研发", "duplicate-head", "重复部门列", "普通员工"))
        );

        BusinessException exception = assertThrows(
                BusinessException.class,
                () -> service.importAccounts(mockMultipartFile(workbook), new LoginAccount())
        );

        assertTrue(exception.getMessage().contains("部门列不能重复"));
    }

    /**
     * 验证角色为空或仅包含分隔符时返回明确的行级必填错误。
     *
     * @throws Exception 注入测试依赖或构造上传文件失败
     */
    @Test
    public void shouldRejectImportRowsWithoutEffectiveRoles() throws Exception {
        SysAccountServiceImpl service = spy(new SysAccountServiceImpl());
        SysDeptMapper deptMapper = mock(SysDeptMapper.class);
        SysRoleMapper roleMapper = mock(SysRoleMapper.class);
        SysPositionMapper positionMapper = mock(SysPositionMapper.class);
        PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
        TransactionStatus transactionStatus = mock(TransactionStatus.class);
        SysDept dept = new SysDept();
        dept.setId(BigInteger.ONE);
        dept.setDeptName("研发部");

        when(deptMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of(dept));
        when(roleMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of());
        when(positionMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of());
        when(transactionManager.getTransaction(any(TransactionDefinition.class))).thenReturn(transactionStatus);
        doReturn(0L).when(service).count(any(QueryWrapper.class));
        setField(service, "sysDeptMapper", deptMapper);
        setField(service, "sysRoleMapper", roleMapper);
        setField(service, "sysPositionMapper", positionMapper);
        setField(service, "transactionManager", transactionManager);

        byte[] workbook = createImportWorkbook(
                List.of(
                        List.of("部门名称*"),
                        List.of("登录账号*"),
                        List.of("昵称*"),
                        List.of("角色名称")
                ),
                List.of(
                        List.of("研发部", "empty-role", "空角色", ""),
                        List.of("研发部", "delimiter-role", "分隔符角色", "， , ")
                )
        );
        LoginAccount loginAccount = new LoginAccount();
        loginAccount.setId(BigInteger.TEN);
        SysAccountImportResultVo result = service.importAccounts(
                mockMultipartFile(workbook),
                loginAccount
        );

        assertEquals(2, result.getTotalCount());
        assertEquals(0, result.getSuccessCount());
        assertEquals(2, result.getErrorCount());
        result.getErrorRows().forEach(errorRow -> {
            List<SysAccountImportErrorDetailVo> details = errorRow.getDetails();
            assertTrue(details.stream().anyMatch(detail ->
                    "角色名称".equals(detail.getFieldName())
                            && "角色名称不能为空".equals(detail.getReason())
            ));
        });
        verify(service, never()).save(any(SysAccount.class));
    }

    /**
     * 构造标准账号导入表头。
     *
     * @param departmentHead 部门列名称
     * @return 导入表头
     */
    private List<List<String>> importHeaders(String departmentHead) {
        return List.of(
                List.of(departmentHead),
                List.of("登录账号*"),
                List.of("昵称*"),
                List.of("角色名称*")
        );
    }

    /**
     * 创建具备成功导入所需依赖的账号服务。
     *
     * @param departments 可供匹配的部门集合
     * @return 测试账号服务
     * @throws Exception 注入测试依赖失败
     */
    private SysAccountServiceImpl createReadyImportService(List<SysDept> departments) throws Exception {
        SysAccountServiceImpl service = spy(new SysAccountServiceImpl());
        SysDeptMapper deptMapper = mock(SysDeptMapper.class);
        SysRoleMapper roleMapper = mock(SysRoleMapper.class);
        SysPositionMapper positionMapper = mock(SysPositionMapper.class);
        PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
        TransactionStatus transactionStatus = mock(TransactionStatus.class);
        SysRole role = new SysRole();
        role.setId(BigInteger.valueOf(100));
        role.setRoleName("普通员工");

        when(deptMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(departments);
        when(roleMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of(role));
        when(positionMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of());
        when(transactionManager.getTransaction(any(TransactionDefinition.class))).thenReturn(transactionStatus);
        doReturn(0L).when(service).count(any(QueryWrapper.class));
        doReturn(true).when(service).save(any(SysAccount.class));
        doNothing().when(service).syncRelations(any(SysAccount.class));

        AccountSecurityProperties properties = new AccountSecurityProperties();
        properties.setDefaultResetPassword("Import123!");
        properties.afterPropertiesSet();
        setField(service, "sysDeptMapper", deptMapper);
        setField(service, "sysRoleMapper", roleMapper);
        setField(service, "sysPositionMapper", positionMapper);
        setField(service, "transactionManager", transactionManager);
        setField(service, "accountSecurityProperties", properties);
        return service;
    }

    /**
     * 构造部门测试数据。
     *
     * @param id 部门 ID
     * @param parentId 父部门 ID，0 表示顶级部门
     * @param name 部门名称
     * @return 部门实体
     */
    private SysDept buildDept(long id, long parentId, String name) {
        SysDept department = new SysDept();
        department.setId(BigInteger.valueOf(id));
        department.setTenantId(BigInteger.valueOf(1000000));
        department.setParentId(BigInteger.valueOf(parentId));
        department.setDeptName(name);
        return department;
    }

    /**
     * 构造账号导入登录上下文。
     *
     * @return 登录账号
     */
    private LoginAccount importLoginAccount() {
        LoginAccount account = new LoginAccount();
        account.setId(BigInteger.TEN);
        account.setTenantId(BigInteger.valueOf(1000000));
        return account;
    }

    /**
     * 创建账号导入测试工作簿。
     *
     * @param headers 表头
     * @param rows 数据行
     * @return 工作簿字节
     */
    private byte[] createImportWorkbook(List<List<String>> headers, List<List<String>> rows) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ExcelWriter excelWriter = EasyExcel.write(outputStream).build();
        try {
            WriteSheet sheet = EasyExcel.writerSheet("模板")
                    .head(headers)
                    .build();
            excelWriter.write(rows, sheet);
        } finally {
            excelWriter.finish();
        }
        return outputStream.toByteArray();
    }

    /**
     * 读取工作簿首个工作表的表头。
     *
     * @param workbook 工作簿字节
     * @return 表头文本
     */
    private List<String> readFirstSheetHeaders(byte[] workbook) {
        List<String> headers = new ArrayList<>();
        ReadListener<LinkedHashMap<Integer, Object>> listener =
                new ReadListener<LinkedHashMap<Integer, Object>>() {
                    @Override
                    public void invoke(LinkedHashMap<Integer, Object> data, AnalysisContext context) {
                        // 模板没有数据行，仅检查表头。
                    }

                    @Override
                    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
                        headMap.values().stream()
                                .map(ReadCellData::getStringValue)
                                .filter(value -> value != null && !value.isBlank())
                                .forEach(headers::add);
                    }

                    @Override
                    public void doAfterAllAnalysed(AnalysisContext context) {
                        // 无需汇总数据行。
                    }
                };
        FastExcel.read(new ByteArrayInputStream(workbook), listener)
                .sheet()
                .doRead();
        return headers;
    }

    /**
     * 构造账号导入上传文件。
     *
     * @param workbook 工作簿字节
     * @return 上传文件
     * @throws Exception 模拟输入流失败
     */
    private MultipartFile mockMultipartFile(byte[] workbook) throws Exception {
        MultipartFile file = mock(MultipartFile.class);
        when(file.isEmpty()).thenReturn(workbook.length == 0);
        when(file.getSize()).thenReturn((long) workbook.length);
        when(file.getOriginalFilename()).thenReturn("users.xlsx");
        when(file.getInputStream()).thenAnswer(invocation -> new ByteArrayInputStream(workbook));
        return file;
    }

    /**
     * 设置测试对象的私有字段。
     *
     * @param target 目标对象
     * @param fieldName 字段名
     * @param value 字段值
     * @throws Exception 字段不存在或不可访问
     */
    private void setField(Object target, String fieldName, Object value) throws Exception {
        Field field = SysAccountServiceImpl.class.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(target, value);
    }
}
