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 com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;
import tech.easyflow.common.cache.RedisLockExecutor;
import tech.easyflow.common.constant.enums.EnumAccountType;
import tech.easyflow.common.constant.enums.EnumDataStatus;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.util.StringUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.config.AccountSecurityProperties;
import tech.easyflow.system.entity.*;
import tech.easyflow.system.entity.vo.*;
import tech.easyflow.system.mapper.*;
import tech.easyflow.system.service.SysAccountService;

import javax.annotation.Resource;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.time.Duration;
import java.util.*;
import java.util.function.Function;

/**
 * 用户表 服务层实现。
 *
 * @author ArkLight
 * @since 2025-03-14
 */
@Service
public class SysAccountServiceImpl extends ServiceImpl<SysAccountMapper, SysAccount> implements SysAccountService {

    private static final String ACCOUNT_RELATION_LOCK_KEY_PREFIX = "easyflow:lock:sys:account:relation:";
    private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
    private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);
    private static final long MAX_IMPORT_FILE_SIZE_BYTES = 10L * 1024 * 1024;
    private static final int MAX_IMPORT_ROWS = 5000;
    /** 重名部门错误中最多展示的候选路径数量。 */
    private static final int MAX_IMPORT_DEPARTMENT_CANDIDATE_PATHS = 10;
    private static final String IMPORT_HEAD_DEPT_PATH = "部门路径*";
    /** 兼容已下载的旧版用户导入模板表头。 */
    private static final String IMPORT_HEAD_DEPT_NAME_LEGACY = "部门名称*";
    private static final String IMPORT_HEAD_LOGIN_NAME = "登录账号*";
    private static final String IMPORT_HEAD_NICKNAME = "昵称*";
    private static final String IMPORT_HEAD_MOBILE = "手机号";
    private static final String IMPORT_HEAD_EMAIL = "邮箱";
    private static final String IMPORT_HEAD_STATUS = "状态";
    private static final String IMPORT_HEAD_ROLE_NAMES = "角色名称*";
    /** 兼容已下载的旧版导入模板表头。 */
    private static final String IMPORT_HEAD_ROLE_NAMES_LEGACY = "角色名称";
    private static final String IMPORT_HEAD_POSITION_NAMES = "岗位名称";
    private static final String IMPORT_HEAD_REMARK = "备注";
    private static final String IMPORT_GUIDE_HEAD_ITEM = "说明项";
    private static final String IMPORT_GUIDE_HEAD_CONTENT = "内容";
    private static final String IMPORT_FIELD_DEPT_PATH = "部门路径";
    private static final String IMPORT_FIELD_LOGIN_NAME = "登录账号";
    private static final String IMPORT_FIELD_NICKNAME = "昵称";
    private static final String IMPORT_FIELD_MOBILE = "手机号";
    private static final String IMPORT_FIELD_EMAIL = "邮箱";
    private static final String IMPORT_FIELD_STATUS = "状态";
    private static final String IMPORT_FIELD_ROLE_NAME = "角色名称";
    private static final String IMPORT_FIELD_POSITION_NAME = "岗位名称";
    private static final String IMPORT_FIELD_SYSTEM = "系统";
    private static final String IMPORT_ERROR_DUPLICATED_RESOURCE = "存在重名，请先在管理端处理";

    @Resource
    private SysAccountRoleMapper sysAccountRoleMapper;
    @Resource
    private SysAccountPositionMapper sysAccountPositionMapper;
    @Resource
    private SysRoleMapper sysRoleMapper;
    @Resource
    private SysPositionMapper sysPositionMapper;
    @Resource
    private SysDeptMapper sysDeptMapper;
    @Resource
    private RedisLockExecutor redisLockExecutor;
    @Resource
    private PlatformTransactionManager transactionManager;
    @Resource
    private AccountSecurityProperties accountSecurityProperties;

    /**
     * 批量解析账号展示名称。
     *
     * @param accountIds 账号 ID 集合
     * @return 账号 ID 到展示名称的映射
     */
    @Override
    public Map<BigInteger, String> resolveDisplayNameMap(Collection<BigInteger> accountIds) {
        if (accountIds == null || accountIds.isEmpty()) {
            return Collections.emptyMap();
        }
        LinkedHashSet<BigInteger> normalizedIds = accountIds.stream()
                .filter(Objects::nonNull)
                .collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
        if (normalizedIds.isEmpty()) {
            return Collections.emptyMap();
        }

        QueryWrapper queryWrapper = QueryWrapper.create()
                .select(SysAccount::getId, SysAccount::getNickname, SysAccount::getLoginName)
                .in(SysAccount::getId, normalizedIds);
        List<SysAccount> accounts = list(queryWrapper);
        LinkedHashMap<BigInteger, String> displayNameMap = new LinkedHashMap<>(normalizedIds.size());
        for (SysAccount account : accounts) {
            if (account == null || account.getId() == null) {
                continue;
            }
            displayNameMap.put(account.getId(), resolveDisplayName(account));
        }
        normalizedIds.forEach(id -> displayNameMap.putIfAbsent(id, id.toString()));
        return displayNameMap;
    }

    /**
     * 解析单个账号的展示名称。
     *
     * @param account 账号实体
     * @return 展示名称
     */
    private String resolveDisplayName(SysAccount account) {
        String nickname = trimToNull(account.getNickname());
        if (StringUtil.hasText(nickname)) {
            return nickname;
        }
        String loginName = trimToNull(account.getLoginName());
        if (StringUtil.hasText(loginName)) {
            return loginName;
        }
        return account.getId() == null ? "" : account.getId().toString();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void syncRelations(SysAccount entity) {
        if (entity == null || entity.getId() == null) {
            return;
        }
        redisLockExecutor.executeWithLock(
                ACCOUNT_RELATION_LOCK_KEY_PREFIX + entity.getId(),
                LOCK_WAIT_TIMEOUT,
                LOCK_LEASE_TIMEOUT,
                () -> {
                    List<BigInteger> roleIds = entity.getRoleIds();
                    if (roleIds != null) {
                        QueryWrapper delW = QueryWrapper.create();
                        delW.eq(SysAccountRole::getAccountId, entity.getId());
                        sysAccountRoleMapper.deleteByQuery(delW);
                        Set<BigInteger> uniqueRoleIds = new LinkedHashSet<>(roleIds);
                        if (!uniqueRoleIds.isEmpty()) {
                            List<SysAccountRole> rows = new ArrayList<>(uniqueRoleIds.size());
                            uniqueRoleIds.forEach(roleId -> {
                                if (roleId == null) {
                                    return;
                                }
                                SysAccountRole row = new SysAccountRole();
                                row.setAccountId(entity.getId());
                                row.setRoleId(roleId);
                                rows.add(row);
                            });
                            if (!rows.isEmpty()) {
                                sysAccountRoleMapper.insertBatch(rows);
                            }
                        }
                    }

                    List<BigInteger> positionIds = entity.getPositionIds();
                    if (positionIds != null) {
                        QueryWrapper delW = QueryWrapper.create();
                        delW.eq(SysAccountPosition::getAccountId, entity.getId());
                        sysAccountPositionMapper.deleteByQuery(delW);
                        Set<BigInteger> uniquePositionIds = new LinkedHashSet<>(positionIds);
                        if (!uniquePositionIds.isEmpty()) {
                            List<SysAccountPosition> rows = new ArrayList<>(uniquePositionIds.size());
                            uniquePositionIds.forEach(positionId -> {
                                if (positionId == null) {
                                    return;
                                }
                                SysAccountPosition row = new SysAccountPosition();
                                row.setAccountId(entity.getId());
                                row.setPositionId(positionId);
                                rows.add(row);
                            });
                            if (!rows.isEmpty()) {
                                sysAccountPositionMapper.insertBatch(rows);
                            }
                        }
                    }
                }
        );
    }

    @Override
    public SysAccount getByUsername(String userKey) {
        QueryWrapper w = QueryWrapper.create();
        w.eq(SysAccount::getLoginName, userKey);
        return getOne(w);
    }

    /**
     * 将账号密码重置为系统默认强密码。
     *
     * @param accountId 账号 ID
     * @param operatorId 操作人账号 ID
     * @return 本次重置后使用的明文密码
     */
    @Override
    public String resetPassword(BigInteger accountId, BigInteger operatorId) {
        SysAccount record = getById(accountId);
        if (record == null) {
            throw new BusinessException("用户不存在");
        }
        validateResetPasswordAllowed(record);
        String plainPassword = accountSecurityProperties.getDefaultResetPassword();
        SysAccount update = new SysAccount();
        update.setId(accountId);
        update.setPassword(BCrypt.hashpw(plainPassword));
        update.setPasswordResetRequired(true);
        update.setModified(new Date());
        update.setModifiedBy(operatorId);
        updateById(update);
        StpUtil.kickout(accountId);
        return plainPassword;
    }

    @Override
    public SysAccountBatchActionResultVo removeBatchWithResult(Collection<BigInteger> ids) {
        List<BigInteger> accountIds = normalizeAccountIds(ids);
        SysAccountBatchActionResultVo result = initBatchActionResult(accountIds);
        if (accountIds.isEmpty()) {
            return result;
        }

        for (BigInteger accountId : accountIds) {
            SysAccount record = getById(accountId);
            if (record == null) {
                appendBatchError(result, accountId, null, "用户不存在");
                continue;
            }
            try {
                validateRemoveAllowed(record);
                executeInRowTransaction(() -> {
                    boolean success = removeById(accountId);
                    if (!success) {
                        throw new BusinessException("删除失败");
                    }
                });
                result.setSuccessCount(result.getSuccessCount() + 1);
            } catch (Exception e) {
                appendBatchError(result, accountId, record.getLoginName(), extractErrorMessage(e, "删除失败"));
            }
        }
        result.setErrorCount(result.getErrorItems().size());
        return result;
    }

    @Override
    public SysAccountBatchActionResultVo resetPasswordBatch(Collection<BigInteger> ids, BigInteger operatorId) {
        List<BigInteger> accountIds = normalizeAccountIds(ids);
        SysAccountBatchActionResultVo result = initBatchActionResult(accountIds);
        if (accountIds.isEmpty()) {
            return result;
        }

        for (BigInteger accountId : accountIds) {
            SysAccount record = getById(accountId);
            if (record == null) {
                appendBatchError(result, accountId, null, "用户不存在");
                continue;
            }
            try {
                executeInRowTransaction(() -> resetPassword(accountId, operatorId));
                result.setSuccessCount(result.getSuccessCount() + 1);
            } catch (Exception e) {
                appendBatchError(result, accountId, record.getLoginName(), extractErrorMessage(e, "重置密码失败"));
            }
        }
        result.setErrorCount(result.getErrorItems().size());
        return result;
    }

    @Override
    public SysAccountImportResultVo importAccounts(MultipartFile file, LoginAccount loginAccount) {
        validateImportFile(file);
        List<SysAccountImportRow> rows = parseImportRows(file);
        SysAccountImportResultVo result = new SysAccountImportResultVo();
        result.setTotalCount(rows.size());
        if (rows.isEmpty()) {
            return result;
        }

        QueryWrapper deptQuery = QueryWrapper.create()
                .eq(SysDept::getTenantId, loginAccount.getTenantId());
        DepartmentImportLookup deptLookup = buildDepartmentImportLookup(
                sysDeptMapper.selectListByQuery(deptQuery)
        );
        ImportNameLookup<SysRole> roleLookup = buildImportNameLookup(
                sysRoleMapper.selectListByQuery(
                        QueryWrapper.create().eq(SysRole::getStatus, EnumDataStatus.AVAILABLE.getCode())
                ),
                SysRole::getRoleName
        );
        ImportNameLookup<SysPosition> positionLookup = buildImportNameLookup(
                sysPositionMapper.selectListByQuery(QueryWrapper.create()),
                SysPosition::getPositionName
        );
        for (SysAccountImportRow row : rows) {
            try {
                executeInRowTransaction(() -> importSingleRow(row, loginAccount, deptLookup, roleLookup, positionLookup));
                result.setSuccessCount(result.getSuccessCount() + 1);
            } catch (Exception e) {
                result.setErrorCount(result.getErrorCount() + 1);
                appendImportError(result, row, e);
            }
        }
        return result;
    }

    @Override
    public void writeImportTemplate(OutputStream outputStream) {
        ExcelWriter excelWriter = EasyExcel.write(outputStream).build();
        try {
            WriteSheet templateSheet = EasyExcel.writerSheet("模板")
                    .head(buildImportHeadList())
                    .build();
            excelWriter.write(new ArrayList<>(), templateSheet);

            WriteSheet guideSheet = EasyExcel.writerSheet("填写说明")
                    .head(buildImportGuideHeadList())
                    .build();
            excelWriter.write(buildImportGuideRows(), guideSheet);
        } finally {
            excelWriter.finish();
        }
    }

    private void executeInRowTransaction(Runnable action) {
        TransactionTemplate template = new TransactionTemplate(transactionManager);
        template.executeWithoutResult(status -> {
            try {
                action.run();
            } catch (RuntimeException e) {
                status.setRollbackOnly();
                throw e;
            }
        });
    }

    private void importSingleRow(SysAccountImportRow row,
                                 LoginAccount loginAccount,
                                 DepartmentImportLookup deptLookup,
                                 ImportNameLookup<SysRole> roleLookup,
                                 ImportNameLookup<SysPosition> positionLookup) {
        List<SysAccountImportErrorDetailVo> details = new ArrayList<>();
        String deptName = trimToNull(row.getDeptName());
        String loginName = trimToNull(row.getLoginName());
        String nickname = trimToNull(row.getNickname());
        String mobile = trimToNull(row.getMobile());
        String email = trimToNull(row.getEmail());
        List<String> roleNames = splitCodes(row.getRoleNames());

        if (deptName == null) {
            addImportDetail(details, IMPORT_FIELD_DEPT_PATH, row.getDeptName(), "部门路径不能为空");
        }
        if (loginName == null) {
            addImportDetail(details, IMPORT_FIELD_LOGIN_NAME, row.getLoginName(), "登录账号不能为空");
        } else if (isLoginNameExists(loginName)) {
            addImportDetail(details, IMPORT_FIELD_LOGIN_NAME, row.getLoginName(), "登录账号已存在");
        }
        if (nickname == null) {
            addImportDetail(details, IMPORT_FIELD_NICKNAME, row.getNickname(), "昵称不能为空");
        }
        if (roleNames.isEmpty()) {
            addImportDetail(details, IMPORT_FIELD_ROLE_NAME, row.getRoleNames(), "角色名称不能为空");
        }
        if (mobile != null && !StringUtil.isMobileNumber(mobile)) {
            addImportDetail(details, IMPORT_FIELD_MOBILE, row.getMobile(), "手机号格式不正确");
        }
        if (email != null && !StringUtil.isEmail(email)) {
            addImportDetail(details, IMPORT_FIELD_EMAIL, row.getEmail(), "邮箱格式不正确");
        }

        SysDept dept = resolveDepartment(
                deptName,
                row.isDepartmentPathHeader(),
                deptLookup,
                details
        );
        Integer status = parseStatus(row.getStatus(), details);
        List<BigInteger> roleIds = resolveResourceIds(
                roleNames,
                roleLookup,
                SysRole::getId,
                IMPORT_FIELD_ROLE_NAME,
                details
        );
        List<BigInteger> positionIds = resolveResourceIds(
                splitCodes(row.getPositionNames()),
                positionLookup,
                SysPosition::getId,
                IMPORT_FIELD_POSITION_NAME,
                details
        );

        if (!details.isEmpty()) {
            throw new ImportRowValidationException(details);
        }

        SysAccount entity = new SysAccount();
        entity.setDeptId(dept.getId());
        entity.setTenantId(loginAccount.getTenantId());
        entity.setLoginName(loginName);
        entity.setPassword(BCrypt.hashpw(accountSecurityProperties.getDefaultResetPassword()));
        entity.setPasswordResetRequired(true);
        entity.setAccountType(EnumAccountType.NORMAL.getCode());
        entity.setNickname(nickname);
        entity.setMobile(mobile);
        entity.setEmail(email);
        entity.setStatus(status);
        entity.setRemark(trimToNull(row.getRemark()));
        entity.setCreated(new Date());
        entity.setCreatedBy(loginAccount.getId());
        entity.setModified(new Date());
        entity.setModifiedBy(loginAccount.getId());
        entity.setRoleIds(roleIds);
        entity.setPositionIds(positionIds);
        save(entity);
        syncRelations(entity);
    }

    /**
     * 校验登录账号是否已存在。
     *
     * @param loginName 登录账号
     * @return 是否已存在
     */
    private boolean isLoginNameExists(String loginName) {
        QueryWrapper wrapper = QueryWrapper.create();
        wrapper.eq(SysAccount::getLoginName, loginName);
        return count(wrapper) > 0;
    }

    /**
     * 解析导入状态列。
     *
     * @param rawStatus 原始状态文本
     * @param details   错误明细收集器
     * @return 解析后的状态编码
     */
    private Integer parseStatus(String rawStatus, List<SysAccountImportErrorDetailVo> details) {
        String status = trimToNull(rawStatus);
        if (status == null) {
            return EnumDataStatus.AVAILABLE.getCode();
        }
        if ("1".equals(status) || "已启用".equals(status) || "启用".equals(status)) {
            return EnumDataStatus.AVAILABLE.getCode();
        }
        if ("0".equals(status) || "未启用".equals(status) || "停用".equals(status) || "禁用".equals(status)) {
            return EnumDataStatus.UNAVAILABLE.getCode();
        }
        addImportDetail(details, IMPORT_FIELD_STATUS, rawStatus, "状态不合法，仅支持 1/0/已启用/启用/未启用/停用/禁用");
        return null;
    }

    /**
     * 解析名称列表并映射为主键集合。
     *
     * @param names      标准化后的名称列表
     * @param lookup     名称查找表
     * @param fieldName  字段名称
     * @param details    错误明细收集器
     * @param <T>        资源类型
     * @return 匹配到的主键集合
     */
    private <T> List<BigInteger> resolveResourceIds(
            List<String> names,
            ImportNameLookup<T> lookup,
            Function<T, BigInteger> idExtractor,
            String fieldName,
            List<SysAccountImportErrorDetailVo> details) {
        if (names.isEmpty()) {
            return Collections.emptyList();
        }
        List<BigInteger> ids = new ArrayList<>(names.size());
        for (String name : names) {
            T resource = resolveSingleResource(name, lookup, fieldName, details);
            if (resource != null) {
                ids.add(idExtractor.apply(resource));
            }
        }
        return ids;
    }

    private List<String> splitCodes(String rawCodes) {
        String codes = trimToNull(rawCodes);
        if (codes == null) {
            return Collections.emptyList();
        }
        String[] values = codes.split("[,，]");
        List<String> result = new ArrayList<>();
        Set<String> uniqueValues = new LinkedHashSet<>();
        for (String value : values) {
            String trimmed = trimToNull(value);
            if (trimmed != null && uniqueValues.add(trimmed)) {
                result.add(trimmed);
            }
        }
        return result;
    }

    /**
     * 构建导入名称查找表，并标记重名数据。
     *
     * @param resources      资源集合
     * @param nameExtractor  名称提取器
     * @param <T>            资源类型
     * @return 名称查找表
     */
    private <T> ImportNameLookup<T> buildImportNameLookup(List<T> resources, Function<T, String> nameExtractor) {
        Map<String, T> uniqueMap = new HashMap<>();
        Set<String> duplicateNames = new LinkedHashSet<>();
        for (T resource : resources) {
            String name = trimToNull(nameExtractor.apply(resource));
            if (name == null) {
                continue;
            }
            if (uniqueMap.containsKey(name)) {
                duplicateNames.add(name);
                uniqueMap.remove(name);
                continue;
            }
            if (!duplicateNames.contains(name)) {
                uniqueMap.put(name, resource);
            }
        }
        return new ImportNameLookup<>(uniqueMap, duplicateNames);
    }

    /**
     * 构建账号导入使用的部门名称与完整路径查找表。
     *
     * <p>每个 {@code parentId=0} 的部门都作为独立顶级分支，路径只从当前分支的顶级部门开始。
     * 构建过程仅使用直接父级关系，并对断链和循环层级进行隔离，避免生成错误路径。</p>
     *
     * @param departments 当前租户的部门集合
     * @return 部门导入查找表
     */
    private DepartmentImportLookup buildDepartmentImportLookup(List<SysDept> departments) {
        List<SysDept> safeDepartments = departments == null ? Collections.emptyList() : departments;
        Map<BigInteger, SysDept> departmentById = new HashMap<>(safeDepartments.size());
        for (SysDept department : safeDepartments) {
            if (department != null && department.getId() != null) {
                departmentById.put(department.getId(), department);
            }
        }

        Map<BigInteger, String> pathById = new HashMap<>(departmentById.size());
        Set<BigInteger> invalidDepartmentIds = new LinkedHashSet<>();
        Set<BigInteger> validDepartmentIds = new LinkedHashSet<>();
        Map<String, SysDept> uniquePathMap = new HashMap<>(departmentById.size());
        Set<String> duplicatePaths = new LinkedHashSet<>();
        Map<String, Set<String>> pathSetByName = new HashMap<>(departmentById.size());
        for (SysDept department : departmentById.values()) {
            String path = resolveDepartmentPath(
                    department,
                    departmentById,
                    pathById,
                    invalidDepartmentIds
            );
            if (path == null) {
                continue;
            }
            validDepartmentIds.add(department.getId());
            pathSetByName.computeIfAbsent(
                    trimToNull(department.getDeptName()),
                    ignored -> new LinkedHashSet<>()
            ).add(path);
            if (uniquePathMap.containsKey(path)) {
                duplicatePaths.add(path);
                uniquePathMap.remove(path);
                continue;
            }
            if (!duplicatePaths.contains(path)) {
                uniquePathMap.put(path, department);
            }
        }
        Map<String, List<String>> pathsByName = new HashMap<>(pathSetByName.size());
        pathSetByName.forEach((name, paths) -> {
            List<String> sortedPaths = new ArrayList<>(paths);
            Collections.sort(sortedPaths);
            pathsByName.put(name, sortedPaths);
        });
        return new DepartmentImportLookup(
                uniquePathMap,
                duplicatePaths,
                validDepartmentIds,
                buildImportNameLookup(new ArrayList<>(departmentById.values()), SysDept::getDeptName),
                pathsByName
        );
    }

    /**
     * 沿直接父级关系解析单个部门的完整路径。
     *
     * @param department 当前部门
     * @param departmentById 部门 ID 索引
     * @param pathById 已解析路径缓存
     * @param invalidDepartmentIds 层级异常的部门 ID
     * @return 从所属顶级部门开始的完整路径；层级异常时返回 {@code null}
     */
    private String resolveDepartmentPath(
            SysDept department,
            Map<BigInteger, SysDept> departmentById,
            Map<BigInteger, String> pathById,
            Set<BigInteger> invalidDepartmentIds) {
        BigInteger departmentId = department.getId();
        if (departmentId == null || invalidDepartmentIds.contains(departmentId)) {
            return null;
        }
        String cachedPath = pathById.get(departmentId);
        if (cachedPath != null) {
            return cachedPath;
        }

        List<SysDept> unresolvedChain = new ArrayList<>();
        Set<BigInteger> visitingIds = new LinkedHashSet<>();
        SysDept current = department;
        String parentPath = null;
        while (current != null) {
            BigInteger currentId = current.getId();
            String currentName = trimToNull(current.getDeptName());
            if (currentId == null
                    || invalidDepartmentIds.contains(currentId)
                    || !visitingIds.add(currentId)
                    || currentName == null
                    || currentName.contains("/")) {
                invalidDepartmentIds.addAll(visitingIds);
                return null;
            }

            String currentCachedPath = pathById.get(currentId);
            if (currentCachedPath != null) {
                parentPath = currentCachedPath;
                break;
            }
            unresolvedChain.add(current);

            BigInteger parentId = current.getParentId();
            if (parentId == null) {
                invalidDepartmentIds.addAll(visitingIds);
                return null;
            }
            if (BigInteger.ZERO.equals(parentId)) {
                break;
            }
            current = departmentById.get(parentId);
            if (current == null) {
                invalidDepartmentIds.addAll(visitingIds);
                return null;
            }
        }

        for (int index = unresolvedChain.size() - 1; index >= 0; index--) {
            SysDept item = unresolvedChain.get(index);
            String itemName = trimToNull(item.getDeptName());
            parentPath = parentPath == null ? itemName : parentPath + "/" + itemName;
            pathById.put(item.getId(), parentPath);
        }
        return pathById.get(departmentId);
    }

    /**
     * 按模板语义解析完整路径或兼容的唯一部门名称。
     *
     * @param rawPath Excel 中填写的部门路径
     * @param departmentPathHeader 是否使用当前部门路径表头
     * @param lookup 部门查找表
     * @param details 错误明细收集器
     * @return 匹配到的部门；校验失败时返回 {@code null}
     */
    private SysDept resolveDepartment(
            String rawPath,
            boolean departmentPathHeader,
            DepartmentImportLookup lookup,
            List<SysAccountImportErrorDetailVo> details) {
        String path = trimToNull(rawPath);
        if (path == null) {
            return null;
        }
        boolean explicitPath = path.contains("/");
        if (departmentPathHeader || explicitPath) {
            String normalizedPath = normalizeDepartmentPath(rawPath, details);
            if (normalizedPath == null) {
                return null;
            }
            if (lookup.getDuplicatePaths().contains(normalizedPath)) {
                addImportDetail(
                        details,
                        IMPORT_FIELD_DEPT_PATH,
                        rawPath,
                        "部门完整路径存在重名，请先在部门管理中处理"
                );
                return null;
            }
            SysDept pathDepartment = lookup.getUniquePathMap().get(normalizedPath);
            if (pathDepartment != null) {
                return pathDepartment;
            }
            if (explicitPath) {
                addImportDetail(
                        details,
                        IMPORT_FIELD_DEPT_PATH,
                        rawPath,
                        "部门路径不存在或层级关系异常，请从所属分支的顶级部门开始填写"
                );
                return null;
            }
        }

        ImportNameLookup<SysDept> nameLookup = lookup.getNameLookup();
        if (nameLookup.getDuplicateNames().contains(path)) {
            addImportDetail(
                    details,
                    IMPORT_FIELD_DEPT_PATH,
                    rawPath,
                    buildDuplicatedDepartmentNameReason(path, lookup)
            );
            return null;
        }
        SysDept department = nameLookup.getUniqueMap().get(path);
        if (department == null) {
            addImportDetail(details, IMPORT_FIELD_DEPT_PATH, rawPath, "部门不存在");
            return null;
        }
        if (!lookup.getValidDepartmentIds().contains(department.getId())) {
            addImportDetail(
                    details,
                    IMPORT_FIELD_DEPT_PATH,
                    rawPath,
                    "部门层级关系异常，请先在部门管理中处理"
            );
            return null;
        }
        return department;
    }

    /**
     * 构建部门名称重名时的完整路径提示。
     *
     * @param departmentName 重复的部门名称
     * @param lookup 部门查找表
     * @return 包含有效候选路径的错误原因
     */
    private String buildDuplicatedDepartmentNameReason(
            String departmentName,
            DepartmentImportLookup lookup) {
        String reason = "部门名称存在重名，请填写从所属分支顶级部门开始的完整路径";
        List<String> candidatePaths = lookup.getPathsByName().get(departmentName);
        if (candidatePaths == null || candidatePaths.size() < 2) {
            return reason;
        }
        int visibleCount = Math.min(candidatePaths.size(), MAX_IMPORT_DEPARTMENT_CANDIDATE_PATHS);
        String visiblePaths = String.join("、", candidatePaths.subList(0, visibleCount));
        if (visibleCount == candidatePaths.size()) {
            return reason + "：" + visiblePaths;
        }
        return reason + "：" + visiblePaths
                + "（共" + candidatePaths.size() + "个候选部门，仅展示前" + visibleCount + "个）";
    }

    /**
     * 规范化部门路径的各级名称。
     *
     * @param rawPath 原始部门路径
     * @param details 错误明细收集器
     * @return 使用英文斜杠连接的规范路径；格式错误时返回 {@code null}
     */
    private String normalizeDepartmentPath(
            String rawPath,
            List<SysAccountImportErrorDetailVo> details) {
        String[] segments = rawPath.trim().split("/", -1);
        List<String> normalizedSegments = new ArrayList<>(segments.length);
        for (String segment : segments) {
            String normalizedSegment = trimToNull(segment);
            if (normalizedSegment == null) {
                addImportDetail(
                        details,
                        IMPORT_FIELD_DEPT_PATH,
                        rawPath,
                        "部门路径格式不正确，层级名称不能为空且必须使用英文 / 分隔"
                );
                return null;
            }
            normalizedSegments.add(normalizedSegment);
        }
        return String.join("/", normalizedSegments);
    }

    private List<SysAccountImportRow> parseImportRows(MultipartFile file) {
        try (InputStream inputStream = file.getInputStream()) {
            SysAccountExcelReadListener listener = new SysAccountExcelReadListener();
            FastExcel.read(inputStream, listener)
                    .sheet()
                    .doRead();
            return listener.getRows();
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            throw new BusinessException("用户导入文件解析失败");
        }
    }

    private void validateImportFile(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new BusinessException("导入文件不能为空");
        }
        if (file.getSize() > MAX_IMPORT_FILE_SIZE_BYTES) {
            throw new BusinessException("导入文件大小不能超过10MB");
        }
        String fileName = file.getOriginalFilename();
        String lowerName = fileName == null ? "" : fileName.toLowerCase();
        if (!(lowerName.endsWith(".xlsx") || lowerName.endsWith(".xls"))) {
            throw new BusinessException("仅支持 xlsx/xls 文件");
        }
    }

    private void appendImportError(SysAccountImportResultVo result, SysAccountImportRow row, Exception exception) {
        SysAccountImportErrorRowVo errorRow = new SysAccountImportErrorRowVo();
        errorRow.setRowNumber(row.getRowNumber());
        errorRow.setDeptName(row.getDeptName());
        errorRow.setLoginName(row.getLoginName());
        errorRow.setNickname(row.getNickname());
        errorRow.setRoleNames(row.getRoleNames());
        errorRow.setPositionNames(row.getPositionNames());
        if (exception instanceof ImportRowValidationException validationException) {
            errorRow.setDetails(validationException.getDetails());
        } else {
            List<SysAccountImportErrorDetailVo> details = new ArrayList<>(1);
            addImportDetail(details, IMPORT_FIELD_SYSTEM, null, extractImportErrorMessage(exception));
            errorRow.setDetails(details);
        }
        result.getErrorRows().add(errorRow);
    }

    private void appendBatchError(SysAccountBatchActionResultVo result, BigInteger id, String loginName, String reason) {
        SysAccountBatchActionErrorItemVo errorItem = new SysAccountBatchActionErrorItemVo();
        errorItem.setId(id);
        errorItem.setLoginName(loginName);
        errorItem.setReason(reason);
        result.getErrorItems().add(errorItem);
    }

    private String extractImportErrorMessage(Exception e) {
        return extractErrorMessage(e, "导入失败");
    }

    private String extractErrorMessage(Exception e, String defaultMessage) {
        if (e == null) {
            return defaultMessage;
        }
        if (e.getCause() != null && StringUtil.hasText(e.getCause().getMessage())) {
            return e.getCause().getMessage();
        }
        if (StringUtil.hasText(e.getMessage())) {
            return e.getMessage();
        }
        return defaultMessage;
    }

    private void validateResetPasswordAllowed(SysAccount record) {
        Integer accountType = record.getAccountType();
        if (EnumAccountType.SUPER_ADMIN.getCode().equals(accountType)) {
            throw new BusinessException("不能重置超级管理员密码");
        }
        if (EnumAccountType.TENANT_ADMIN.getCode().equals(accountType)) {
            throw new BusinessException("不能重置租户管理员密码");
        }
    }

    private void validateRemoveAllowed(SysAccount record) {
        Integer accountType = record.getAccountType();
        if (EnumAccountType.SUPER_ADMIN.getCode().equals(accountType)) {
            throw new BusinessException("不能删除超级管理员");
        }
        if (EnumAccountType.TENANT_ADMIN.getCode().equals(accountType)) {
            throw new BusinessException("不能删除租户管理员");
        }
    }

    private List<BigInteger> normalizeAccountIds(Collection<BigInteger> ids) {
        if (ids == null || ids.isEmpty()) {
            return Collections.emptyList();
        }
        LinkedHashSet<BigInteger> uniqueIds = new LinkedHashSet<>();
        for (BigInteger id : ids) {
            if (id != null) {
                uniqueIds.add(id);
            }
        }
        return new ArrayList<>(uniqueIds);
    }

    private SysAccountBatchActionResultVo initBatchActionResult(List<BigInteger> accountIds) {
        SysAccountBatchActionResultVo result = new SysAccountBatchActionResultVo();
        result.setTotalCount(accountIds.size());
        return result;
    }

    private List<List<String>> buildImportHeadList() {
        List<List<String>> headList = new ArrayList<>(9);
        headList.add(Collections.singletonList(IMPORT_HEAD_DEPT_PATH));
        headList.add(Collections.singletonList(IMPORT_HEAD_LOGIN_NAME));
        headList.add(Collections.singletonList(IMPORT_HEAD_NICKNAME));
        headList.add(Collections.singletonList(IMPORT_HEAD_MOBILE));
        headList.add(Collections.singletonList(IMPORT_HEAD_EMAIL));
        headList.add(Collections.singletonList(IMPORT_HEAD_STATUS));
        headList.add(Collections.singletonList(IMPORT_HEAD_ROLE_NAMES));
        headList.add(Collections.singletonList(IMPORT_HEAD_POSITION_NAMES));
        headList.add(Collections.singletonList(IMPORT_HEAD_REMARK));
        return headList;
    }

    private List<List<String>> buildImportGuideHeadList() {
        List<List<String>> headList = new ArrayList<>(2);
        headList.add(Collections.singletonList(IMPORT_GUIDE_HEAD_ITEM));
        headList.add(Collections.singletonList(IMPORT_GUIDE_HEAD_CONTENT));
        return headList;
    }

    private List<List<String>> buildImportGuideRows() {
        List<List<String>> rows = new ArrayList<>();
        rows.add(List.of(
                "填写规则",
                "部门优先填写从所属分支顶级部门开始的完整路径，使用英文/分隔，例如：石家庄分行/交易银行部；顶级部门直接填写名称，唯一部门名称仍兼容"
        ));
        rows.add(List.of("必填字段", "部门路径*、登录账号*、昵称*、角色名称*"));
        rows.add(List.of("可选字段", "手机号、邮箱、状态、岗位名称、备注"));
        rows.add(List.of("状态可选值", "可留空，或填写 1/0/已启用/启用/未启用/停用/禁用"));
        rows.add(List.of("多值分隔", "角色名称、岗位名称支持使用英文逗号,或中文逗号，分隔多个名称"));
        rows.add(List.of(
                "导入后初始密码",
                "导入成功的账号默认密码为 " + accountSecurityProperties.getDefaultResetPassword()
                        + "，首次登录需要修改密码"
        ));
        rows.add(List.of("示例行", "技术部/研发 | zhangsan | 张三 | 13800138000 | zhangsan@example.com | 已启用 | 普通员工,审批专员 | 产品经理 | 示例导入"));
        return rows;
    }

    private void addImportDetail(List<SysAccountImportErrorDetailVo> details,
                                 String fieldName,
                                 String fieldValue,
                                 String reason) {
        SysAccountImportErrorDetailVo detail = new SysAccountImportErrorDetailVo();
        detail.setFieldName(fieldName);
        detail.setFieldValue(trimToNull(fieldValue));
        detail.setReason(reason);
        details.add(detail);
    }

    private <T> T resolveSingleResource(
            String rawName,
            ImportNameLookup<T> lookup,
            String fieldName,
            List<SysAccountImportErrorDetailVo> details) {
        String name = trimToNull(rawName);
        if (name == null) {
            return null;
        }
        if (lookup.getDuplicateNames().contains(name)) {
            addImportDetail(details, fieldName, rawName, fieldName + IMPORT_ERROR_DUPLICATED_RESOURCE);
            return null;
        }
        T resource = lookup.getUniqueMap().get(name);
        if (resource == null) {
            addImportDetail(details, fieldName, rawName, fieldName + "不存在");
            return null;
        }
        return resource;
    }

    private String trimToNull(String value) {
        if (!StringUtil.hasText(value)) {
            return null;
        }
        return value.trim();
    }

    private static class SysAccountImportRow {
        private Integer rowNumber;
        private String deptName;
        private boolean departmentPathHeader;
        private String loginName;
        private String nickname;
        private String mobile;
        private String email;
        private String status;
        private String roleNames;
        private String positionNames;
        private String remark;

        public Integer getRowNumber() {
            return rowNumber;
        }

        public void setRowNumber(Integer rowNumber) {
            this.rowNumber = rowNumber;
        }

        public String getDeptName() {
            return deptName;
        }

        public void setDeptName(String deptName) {
            this.deptName = deptName;
        }

        /**
         * 判断当前行是否来自部门路径表头。
         *
         * @return 使用部门路径表头时返回 {@code true}
         */
        public boolean isDepartmentPathHeader() {
            return departmentPathHeader;
        }

        /**
         * 设置当前行是否来自部门路径表头。
         *
         * @param departmentPathHeader 是否使用部门路径表头
         */
        public void setDepartmentPathHeader(boolean departmentPathHeader) {
            this.departmentPathHeader = departmentPathHeader;
        }

        public String getLoginName() {
            return loginName;
        }

        public void setLoginName(String loginName) {
            this.loginName = loginName;
        }

        public String getNickname() {
            return nickname;
        }

        public void setNickname(String nickname) {
            this.nickname = nickname;
        }

        public String getMobile() {
            return mobile;
        }

        public void setMobile(String mobile) {
            this.mobile = mobile;
        }

        public String getEmail() {
            return email;
        }

        public void setEmail(String email) {
            this.email = email;
        }

        public String getStatus() {
            return status;
        }

        public void setStatus(String status) {
            this.status = status;
        }

        public String getRoleNames() {
            return roleNames;
        }

        public void setRoleNames(String roleNames) {
            this.roleNames = roleNames;
        }

        public String getPositionNames() {
            return positionNames;
        }

        public void setPositionNames(String positionNames) {
            this.positionNames = positionNames;
        }

        public String getRemark() {
            return remark;
        }

        public void setRemark(String remark) {
            this.remark = remark;
        }
    }

    private class SysAccountExcelReadListener implements ReadListener<LinkedHashMap<Integer, Object>> {

        private final Map<String, Integer> headIndex = new HashMap<>();
        private final List<SysAccountImportRow> rows = new ArrayList<>();
        private int sheetRowNo;

        @Override
        public void invoke(LinkedHashMap<Integer, Object> data, AnalysisContext context) {
            sheetRowNo++;
            String deptName = getCellValue(data, IMPORT_HEAD_DEPT_PATH, IMPORT_HEAD_DEPT_NAME_LEGACY);
            String loginName = getCellValue(data, IMPORT_HEAD_LOGIN_NAME);
            String nickname = getCellValue(data, IMPORT_HEAD_NICKNAME);
            String mobile = getCellValue(data, IMPORT_HEAD_MOBILE);
            String email = getCellValue(data, IMPORT_HEAD_EMAIL);
            String status = getCellValue(data, IMPORT_HEAD_STATUS);
            String roleNames = getCellValue(data, IMPORT_HEAD_ROLE_NAMES, IMPORT_HEAD_ROLE_NAMES_LEGACY);
            String positionNames = getCellValue(data, IMPORT_HEAD_POSITION_NAMES);
            String remark = getCellValue(data, IMPORT_HEAD_REMARK);
            if (!StringUtil.hasText(deptName)
                    && !StringUtil.hasText(loginName)
                    && !StringUtil.hasText(nickname)
                    && !StringUtil.hasText(mobile)
                    && !StringUtil.hasText(email)
                    && !StringUtil.hasText(status)
                    && !StringUtil.hasText(roleNames)
                    && !StringUtil.hasText(positionNames)
                    && !StringUtil.hasText(remark)) {
                return;
            }
            if (rows.size() >= MAX_IMPORT_ROWS) {
                throw new BusinessException("单次最多导入5000个用户");
            }
            SysAccountImportRow row = new SysAccountImportRow();
            row.setRowNumber(sheetRowNo + 1);
            row.setDeptName(deptName);
            row.setDepartmentPathHeader(headIndex.containsKey(IMPORT_HEAD_DEPT_PATH));
            row.setLoginName(loginName);
            row.setNickname(nickname);
            row.setMobile(mobile);
            row.setEmail(email);
            row.setStatus(status);
            row.setRoleNames(roleNames);
            row.setPositionNames(positionNames);
            row.setRemark(remark);
            rows.add(row);
        }

        @Override
        public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
            for (Map.Entry<Integer, ReadCellData<?>> entry : headMap.entrySet()) {
                String headValue = entry.getValue() == null ? null : entry.getValue().getStringValue();
                String header = trimToNull(headValue);
                if (header != null) {
                    Integer previousIndex = headIndex.putIfAbsent(header, entry.getKey());
                    if (previousIndex != null
                            && (IMPORT_HEAD_DEPT_PATH.equals(header)
                            || IMPORT_HEAD_DEPT_NAME_LEGACY.equals(header))) {
                        throw new BusinessException("导入模板表头不正确，部门列不能重复");
                    }
                }
            }
            boolean hasDeptPathHead = headIndex.containsKey(IMPORT_HEAD_DEPT_PATH);
            boolean hasLegacyDeptNameHead = headIndex.containsKey(IMPORT_HEAD_DEPT_NAME_LEGACY);
            if (hasDeptPathHead && hasLegacyDeptNameHead) {
                throw new BusinessException("导入模板表头不正确，部门路径*与部门名称*只能保留一个");
            }
            List<String> requiredHeads = List.of(
                    IMPORT_HEAD_DEPT_PATH,
                    IMPORT_HEAD_LOGIN_NAME,
                    IMPORT_HEAD_NICKNAME,
                    IMPORT_HEAD_ROLE_NAMES
            );
            for (String requiredHead : requiredHeads) {
                if (IMPORT_HEAD_DEPT_PATH.equals(requiredHead) && hasLegacyDeptNameHead) {
                    continue;
                }
                if (IMPORT_HEAD_ROLE_NAMES.equals(requiredHead)
                        && headIndex.containsKey(IMPORT_HEAD_ROLE_NAMES_LEGACY)) {
                    continue;
                }
                if (!headIndex.containsKey(requiredHead)) {
                    throw new BusinessException("导入模板表头不正确，必须包含：" + String.join("、", requiredHeads));
                }
            }
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            // no-op
        }

        public List<SysAccountImportRow> getRows() {
            return rows;
        }

        /**
         * 按候选表头顺序读取单元格文本。
         *
         * @param row 当前数据行
         * @param headNames 候选表头
         * @return 单元格文本，无匹配表头时返回 {@code null}
         */
        private String getCellValue(Map<Integer, Object> row, String... headNames) {
            for (String headName : headNames) {
                Integer index = headIndex.get(headName);
                if (index == null) {
                    continue;
                }
                Object value = row.get(index);
                return value == null ? null : String.valueOf(value).trim();
            }
            return null;
        }
    }

    /**
     * 账号导入的部门路径与兼容名称查找表。
     */
    private static class DepartmentImportLookup {
        private final Map<String, SysDept> uniquePathMap;
        private final Set<String> duplicatePaths;
        private final Set<BigInteger> validDepartmentIds;
        private final ImportNameLookup<SysDept> nameLookup;
        private final Map<String, List<String>> pathsByName;

        /**
         * 创建部门导入查找表。
         *
         * @param uniquePathMap 唯一完整路径映射
         * @param duplicatePaths 重复完整路径集合
         * @param validDepartmentIds 层级关系有效的部门 ID 集合
         * @param nameLookup 兼容名称查找表
         * @param pathsByName 部门名称到有效完整路径列表的映射
         */
        private DepartmentImportLookup(
                Map<String, SysDept> uniquePathMap,
                Set<String> duplicatePaths,
                Set<BigInteger> validDepartmentIds,
                ImportNameLookup<SysDept> nameLookup,
                Map<String, List<String>> pathsByName) {
            this.uniquePathMap = uniquePathMap;
            this.duplicatePaths = duplicatePaths;
            this.validDepartmentIds = validDepartmentIds;
            this.nameLookup = nameLookup;
            this.pathsByName = pathsByName;
        }

        /**
         * 获取唯一完整路径映射。
         *
         * @return 唯一完整路径映射
         */
        public Map<String, SysDept> getUniquePathMap() {
            return uniquePathMap;
        }

        /**
         * 获取重复完整路径集合。
         *
         * @return 重复完整路径集合
         */
        public Set<String> getDuplicatePaths() {
            return duplicatePaths;
        }

        /**
         * 获取层级关系有效的部门 ID 集合。
         *
         * @return 有效部门 ID 集合
         */
        public Set<BigInteger> getValidDepartmentIds() {
            return validDepartmentIds;
        }

        /**
         * 获取兼容名称查找表。
         *
         * @return 名称查找表
         */
        public ImportNameLookup<SysDept> getNameLookup() {
            return nameLookup;
        }

        /**
         * 获取部门名称对应的有效完整路径列表。
         *
         * @return 名称到完整路径列表的映射
         */
        public Map<String, List<String>> getPathsByName() {
            return pathsByName;
        }
    }

    private static class ImportNameLookup<T> {
        private final Map<String, T> uniqueMap;
        private final Set<String> duplicateNames;

        private ImportNameLookup(Map<String, T> uniqueMap, Set<String> duplicateNames) {
            this.uniqueMap = uniqueMap;
            this.duplicateNames = duplicateNames;
        }

        public Map<String, T> getUniqueMap() {
            return uniqueMap;
        }

        public Set<String> getDuplicateNames() {
            return duplicateNames;
        }
    }

    private static class ImportRowValidationException extends RuntimeException {
        private final List<SysAccountImportErrorDetailVo> details;

        private ImportRowValidationException(List<SysAccountImportErrorDetailVo> details) {
            super("导入数据校验失败");
            this.details = details;
        }

        public List<SysAccountImportErrorDetailVo> getDetails() {
            return details;
        }
    }
}
