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.annotation.Transactional;
import org.springframework.transaction.PlatformTransactionManager;
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.entity.SysAccount;
import tech.easyflow.system.entity.SysAccountPosition;
import tech.easyflow.system.entity.SysAccountRole;
import tech.easyflow.system.entity.SysDept;
import tech.easyflow.system.entity.SysPosition;
import tech.easyflow.system.entity.SysRole;
import tech.easyflow.system.entity.vo.SysAccountBatchActionErrorItemVo;
import tech.easyflow.system.entity.vo.SysAccountBatchActionResultVo;
import tech.easyflow.system.entity.vo.SysAccountImportErrorDetailVo;
import tech.easyflow.system.entity.vo.SysAccountImportErrorRowVo;
import tech.easyflow.system.entity.vo.SysAccountImportResultVo;
import tech.easyflow.system.mapper.SysAccountMapper;
import tech.easyflow.system.mapper.SysAccountPositionMapper;
import tech.easyflow.system.mapper.SysAccountRoleMapper;
import tech.easyflow.system.mapper.SysDeptMapper;
import tech.easyflow.system.mapper.SysPositionMapper;
import tech.easyflow.system.mapper.SysRoleMapper;
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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
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 String DEFAULT_RESET_PASSWORD = "123456";
    private static final long MAX_IMPORT_FILE_SIZE_BYTES = 10L * 1024 * 1024;
    private static final int MAX_IMPORT_ROWS = 5000;
    private static final String IMPORT_HEAD_DEPT_NAME = "部门名称*";
    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_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_NAME = "部门名称";
    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;

    /**
     * 批量解析账号展示名称。
     *
     * @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);
    }

    @Override
    public void resetPassword(BigInteger accountId, BigInteger operatorId) {
        SysAccount record = getById(accountId);
        if (record == null) {
            throw new BusinessException("用户不存在");
        }
        validateResetPasswordAllowed(record);
        SysAccount update = new SysAccount();
        update.setId(accountId);
        update.setPassword(BCrypt.hashpw(DEFAULT_RESET_PASSWORD));
        update.setPasswordResetRequired(true);
        update.setModified(new Date());
        update.setModifiedBy(operatorId);
        updateById(update);
        StpUtil.kickout(accountId);
    }

    @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;
        }

        ImportNameLookup<SysDept> deptLookup = buildImportNameLookup(
                sysDeptMapper.selectListByQuery(QueryWrapper.create()),
                SysDept::getDeptName
        );
        ImportNameLookup<SysRole> roleLookup = buildImportNameLookup(
                sysRoleMapper.selectListByQuery(QueryWrapper.create()),
                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,
                                 ImportNameLookup<SysDept> 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());

        if (deptName == null) {
            addImportDetail(details, IMPORT_FIELD_DEPT_NAME, 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 (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 = resolveSingleResource(deptName, deptLookup, IMPORT_FIELD_DEPT_NAME, details);
        Integer status = parseStatus(row.getStatus(), details);
        List<BigInteger> roleIds = resolveResourceIds(
                row.getRoleNames(),
                roleLookup,
                SysRole::getId,
                IMPORT_FIELD_ROLE_NAME,
                details
        );
        List<BigInteger> positionIds = resolveResourceIds(
                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(DEFAULT_RESET_PASSWORD));
        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 rawNames   原始名称文本
     * @param lookup     名称查找表
     * @param fieldName  字段名称
     * @param details    错误明细收集器
     * @param <T>        资源类型
     * @return 匹配到的主键集合
     */
    private <T> List<BigInteger> resolveResourceIds(
            String rawNames,
            ImportNameLookup<T> lookup,
            Function<T, BigInteger> idExtractor,
            String fieldName,
            List<SysAccountImportErrorDetailVo> details) {
        List<String> names = splitCodes(rawNames);
        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);
    }

    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_NAME));
        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("导入后初始密码", "导入成功的账号默认密码为 123456，首次登录需要修改密码"));
        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 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;
        }

        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_NAME);
            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);
            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.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) {
                    headIndex.put(header, entry.getKey());
                }
            }
            List<String> requiredHeads = List.of(
                    IMPORT_HEAD_DEPT_NAME,
                    IMPORT_HEAD_LOGIN_NAME,
                    IMPORT_HEAD_NICKNAME
            );
            for (String requiredHead : requiredHeads) {
                if (!headIndex.containsKey(requiredHead)) {
                    throw new BusinessException("导入模板表头不正确，必须包含：" + String.join("、", requiredHeads));
                }
            }
        }

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

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

        private String getCellValue(Map<Integer, Object> row, String headName) {
            Integer index = headIndex.get(headName);
            if (index == null) {
                return null;
            }
            Object value = row.get(index);
            return value == null ? null : String.valueOf(value).trim();
        }
    }

    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;
        }
    }
}
