package tech.easyflow.approval.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.approval.entity.ApprovalFlowStep;
import tech.easyflow.approval.entity.ApprovalInstance;
import tech.easyflow.approval.entity.ApprovalLog;
import tech.easyflow.approval.entity.ApprovalTask;
import tech.easyflow.approval.entity.vo.ApprovalInstanceDetailVo;
import tech.easyflow.approval.entity.vo.ApprovalInstancePageVo;
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
import tech.easyflow.approval.entity.vo.ApprovalLogVo;
import tech.easyflow.approval.entity.vo.ApprovalTaskVo;
import tech.easyflow.approval.enums.ApprovalActionType;
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
import tech.easyflow.approval.enums.ApprovalResourceType;
import tech.easyflow.approval.enums.ApprovalTaskStatus;
import tech.easyflow.approval.mapper.ApprovalFlowStepMapper;
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
import tech.easyflow.approval.mapper.ApprovalLogMapper;
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
import tech.easyflow.approval.service.ApprovalAssigneeService;
import tech.easyflow.approval.service.ApprovalQueryService;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.service.SysAccountService;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 审批查询服务实现。
 */
@Service
public class ApprovalQueryServiceImpl implements ApprovalQueryService {

    @Resource
    private ApprovalInstanceMapper approvalInstanceMapper;

    @Resource
    private ApprovalTaskMapper approvalTaskMapper;

    @Resource
    private ApprovalLogMapper approvalLogMapper;

    @Resource
    private ApprovalFlowStepMapper approvalFlowStepMapper;

    @Resource
    private ApprovalAssigneeService approvalAssigneeService;

    @Resource
    private SysAccountService sysAccountService;

    /**
     * {@inheritDoc}
     */
    @Override
    public Page<ApprovalInstancePageVo> pendingPage(String resourceType, String actionType, String keyword,
                                                    Long pageNumber, Long pageSize) {
        LoginAccount account = requireLoginAccount();
        Set<BigInteger> roleIds = approvalAssigneeService.getAvailableRoleIds(account.getId());
        Set<BigInteger> instanceIds = approvalAssigneeService.listPendingInstanceIds(account.getId(), roleIds, null);
        if (CollectionUtil.isEmpty(instanceIds)) {
            return new Page<>(List.of(), safePageNumber(pageNumber), safePageSize(pageSize), 0L);
        }
        QueryWrapper queryWrapper = buildBaseQuery(resourceType, actionType, keyword);
        queryWrapper.in(ApprovalInstance::getId, instanceIds);
        queryWrapper.in(ApprovalInstance::getStatus, List.of(
                ApprovalInstanceStatus.PENDING.getCode(),
                ApprovalInstanceStatus.PROCESSING.getCode()));
        queryWrapper.orderBy("submitted_at desc, id desc");
        return mapPage(queryWrapper, safePageNumber(pageNumber), safePageSize(pageSize), true, account, roleIds);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Page<ApprovalInstancePageVo> processedPage(String resourceType, String actionType, String keyword,
                                                      Long pageNumber, Long pageSize) {
        LoginAccount account = requireLoginAccount();
        QueryWrapper actedTaskWrapper = QueryWrapper.create()
                .eq(ApprovalTask::getActedBy, account.getId())
                .in(ApprovalTask::getStatus, List.of(
                        ApprovalTaskStatus.APPROVED.getCode(),
                        ApprovalTaskStatus.REJECTED.getCode(),
                        ApprovalTaskStatus.REVOKED.getCode()));
        List<BigInteger> instanceIds = approvalTaskMapper.selectListByQuery(actedTaskWrapper).stream()
                .map(ApprovalTask::getInstanceId)
                .distinct()
                .collect(Collectors.toList());
        if (CollectionUtil.isEmpty(instanceIds)) {
            return new Page<>(List.of(), safePageNumber(pageNumber), safePageSize(pageSize), 0L);
        }
        QueryWrapper queryWrapper = buildBaseQuery(resourceType, actionType, keyword);
        queryWrapper.in(ApprovalInstance::getId, instanceIds);
        queryWrapper.orderBy("finished_at desc, id desc");
        return mapPage(queryWrapper, safePageNumber(pageNumber), safePageSize(pageSize), false, account, Set.of());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Page<ApprovalInstancePageVo> initiatedPage(String resourceType, String actionType, String keyword,
                                                      Long pageNumber, Long pageSize) {
        LoginAccount account = requireLoginAccount();
        QueryWrapper queryWrapper = buildBaseQuery(resourceType, actionType, keyword);
        queryWrapper.eq(ApprovalInstance::getApplicantId, account.getId());
        queryWrapper.orderBy("submitted_at desc, id desc");
        return mapPage(queryWrapper, safePageNumber(pageNumber), safePageSize(pageSize), false, account, Set.of());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ApprovalInstanceDetailVo detail(BigInteger instanceId) {
        ApprovalInstance instance = approvalInstanceMapper.selectOneById(instanceId);
        if (instance == null) {
            throw new BusinessException("审批实例不存在");
        }
        ApprovalInstanceDetailVo detail = new ApprovalInstanceDetailVo();
        detail.setId(instance.getId());
        detail.setFlowId(instance.getFlowId());
        detail.setFlowVersion(instance.getFlowVersion());
        detail.setResourceType(instance.getResourceType());
        detail.setResourceId(instance.getResourceId());
        detail.setActionType(instance.getActionType());
        detail.setStatus(instance.getStatus());
        detail.setCurrentStepNo(instance.getCurrentStepNo());
        detail.setSummary(instance.getSummary());
        detail.setApplicantId(instance.getApplicantId());
        detail.setSubmittedAt(instance.getSubmittedAt());
        detail.setFinishedAt(instance.getFinishedAt());
        detail.setSnapshotJson(instance.getSnapshotJson());

        List<ApprovalTask> tasks = approvalTaskMapper.selectListByQuery(
                QueryWrapper.create().eq(ApprovalTask::getInstanceId, instanceId));
        List<ApprovalLog> logs = approvalLogMapper.selectListByQuery(
                QueryWrapper.create().eq(ApprovalLog::getInstanceId, instanceId));
        Map<Integer, ApprovalFlowStepVo> frozenStepMap = resolveFrozenStepMap(instance);
        Map<BigInteger, SysAccount> accountMap = loadAccountMap(instance, tasks, logs);
        detail.setApplicantName(resolveAccountName(accountMap.get(instance.getApplicantId())));
        detail.setApplicantAccount(resolveAccountLoginName(accountMap.get(instance.getApplicantId())));

        detail.setTasks(tasks.stream()
                .sorted(Comparator.comparing(ApprovalTask::getStepNo))
                .map(item -> {
                    ApprovalTaskVo taskVo = new ApprovalTaskVo();
                    taskVo.setId(item.getId());
                    taskVo.setStepNo(item.getStepNo());
                    taskVo.setStepName(resolveStepName(frozenStepMap, item.getStepNo()));
                    taskVo.setStatus(item.getStatus());
                    taskVo.setAssigneeRoleCode(item.getAssigneeRoleCode());
                    taskVo.setAssigneeType(item.getAssigneeType());
                    taskVo.setAssigneeTargetId(item.getAssigneeTargetId());
                    taskVo.setAssigneeTargetCode(item.getAssigneeTargetCode());
                    taskVo.setAssigneeTargetName(item.getAssigneeTargetName());
                    taskVo.setActedBy(item.getActedBy());
                    taskVo.setActedByName(resolveAccountName(accountMap.get(item.getActedBy())));
                    taskVo.setActedAt(item.getActedAt());
                    taskVo.setComment(item.getComment());
                    return taskVo;
                })
                .collect(Collectors.toList()));

        detail.setLogs(logs.stream()
                .sorted(Comparator.comparing(ApprovalLog::getCreated))
                .map(item -> {
                    ApprovalLogVo logVo = new ApprovalLogVo();
                    logVo.setId(item.getId());
                    logVo.setEventType(item.getEventType());
                    logVo.setOperatorId(item.getOperatorId());
                    logVo.setOperatorAccount(resolveAccountLoginName(accountMap.get(item.getOperatorId())));
                    logVo.setOperatorName(resolveAccountName(accountMap.get(item.getOperatorId())));
                    logVo.setCreated(item.getCreated());
                    logVo.setPayloadJson(item.getPayloadJson());
                    return logVo;
                })
                .collect(Collectors.toList()));

        LoginAccount account = requireLoginAccount();
        Set<BigInteger> roleIds = approvalAssigneeService.getAvailableRoleIds(account.getId());
        boolean canOperate = !ApprovalInstanceStatus.from(instance.getStatus()).isFinished()
                && tasks.stream().anyMatch(item -> item.getStepNo().equals(instance.getCurrentStepNo())
                && ApprovalTaskStatus.PENDING.getCode().equals(item.getStatus())
                && approvalAssigneeService.canHandleTask(item, account.getId(), roleIds));
        detail.setCanApprove(canOperate);
        detail.setCanReject(canOperate);
        detail.setCanRevoke(canOperate);
        return detail;
    }

    /**
     * 批量加载审批详情里涉及到的账号信息。
     *
     * @param instance 审批实例
     * @param tasks 审批任务列表
     * @param logs 审批日志列表
     * @return 账号 ID 到账号实体的映射
     */
    private Map<BigInteger, SysAccount> loadAccountMap(ApprovalInstance instance, List<ApprovalTask> tasks,
                                                       List<ApprovalLog> logs) {
        Set<BigInteger> accountIds = new HashSet<>();
        if (instance.getApplicantId() != null) {
            accountIds.add(instance.getApplicantId());
        }
        tasks.stream()
                .map(ApprovalTask::getActedBy)
                .filter(id -> id != null)
                .forEach(accountIds::add);
        logs.stream()
                .map(ApprovalLog::getOperatorId)
                .filter(id -> id != null)
                .forEach(accountIds::add);
        if (CollectionUtil.isEmpty(accountIds)) {
            return Map.of();
        }
        return sysAccountService.listByIds(accountIds).stream()
                .collect(Collectors.toMap(
                        SysAccount::getId,
                        account -> account,
                        (left, right) -> left,
                        LinkedHashMap::new));
    }

    /**
     * 解析账号展示名称，优先昵称，其次登录名。
     *
     * @param account 账号实体
     * @return 展示名称
     */
    private String resolveAccountName(SysAccount account) {
        if (account == null) {
            return null;
        }
        if (StringUtils.hasText(account.getNickname())) {
            return account.getNickname().trim();
        }
        if (StringUtils.hasText(account.getLoginName())) {
            return account.getLoginName().trim();
        }
        return null;
    }

    /**
     * 解析账号登录名。
     *
     * @param account 账号实体
     * @return 登录账号
     */
    private String resolveAccountLoginName(SysAccount account) {
        if (account == null || !StringUtils.hasText(account.getLoginName())) {
            return null;
        }
        return account.getLoginName().trim();
    }

    private QueryWrapper buildBaseQuery(String resourceType, String actionType, String keyword) {
        QueryWrapper queryWrapper = QueryWrapper.create();
        if (StringUtils.hasText(resourceType)) {
            queryWrapper.eq(ApprovalInstance::getResourceType, ApprovalResourceType.from(resourceType).getCode());
        }
        if (StringUtils.hasText(actionType)) {
            queryWrapper.eq(ApprovalInstance::getActionType, ApprovalActionType.from(actionType).getCode());
        }
        if (StringUtils.hasText(keyword)) {
            queryWrapper.like(ApprovalInstance::getSummary, keyword.trim());
        }
        return queryWrapper;
    }

    private Page<ApprovalInstancePageVo> mapPage(QueryWrapper queryWrapper, long pageNumber, long pageSize,
                                                 boolean pendingMode, LoginAccount account, Set<BigInteger> roleIds) {
        Page<ApprovalInstance> page = approvalInstanceMapper.paginate(pageNumber, pageSize, queryWrapper);
        List<ApprovalInstance> records = page.getRecords();
        Set<BigInteger> pendingTaskInstanceIds = pendingMode
                ? approvalAssigneeService.listPendingInstanceIds(account.getId(), roleIds,
                records.stream().map(ApprovalInstance::getId).collect(Collectors.toList()))
                : Set.of();

        List<ApprovalInstancePageVo> result = new ArrayList<>();
        for (ApprovalInstance record : records) {
            ApprovalInstancePageVo item = new ApprovalInstancePageVo();
            item.setId(record.getId());
            item.setResourceType(record.getResourceType());
            item.setResourceId(record.getResourceId());
            item.setActionType(record.getActionType());
            item.setStatus(record.getStatus());
            item.setCurrentStepNo(record.getCurrentStepNo());
            item.setCurrentStepName(resolveCurrentStepName(record));
            item.setSummary(record.getSummary());
            item.setApplicantId(record.getApplicantId());
            item.setSubmittedAt(record.getSubmittedAt());
            item.setFinishedAt(record.getFinishedAt());
            boolean canOperate = pendingMode
                    && pendingTaskInstanceIds.contains(record.getId())
                    && !ApprovalInstanceStatus.from(record.getStatus()).isFinished();
            item.setCanApprove(canOperate);
            item.setCanReject(canOperate);
            item.setCanRevoke(canOperate);
            result.add(item);
        }

        Page<ApprovalInstancePageVo> voPage = new Page<>(pageNumber, pageSize, page.getTotalRow());
        voPage.setRecords(result);
        return voPage;
    }

    private long safePageNumber(Long pageNumber) {
        return pageNumber == null || pageNumber < 1 ? 1L : pageNumber;
    }

    private long safePageSize(Long pageSize) {
        return pageSize == null || pageSize < 1 ? 10L : pageSize;
    }

    private LoginAccount requireLoginAccount() {
        LoginAccount account = SaTokenUtil.getLoginAccount();
        if (account == null) {
            throw new BusinessException("当前未登录");
        }
        return account;
    }

    private String resolveCurrentStepName(ApprovalInstance instance) {
        Map<Integer, ApprovalFlowStepVo> stepMap = resolveFrozenStepMap(instance);
        return resolveStepName(stepMap, instance.getCurrentStepNo());
    }

    private String resolveStepName(Map<Integer, ApprovalFlowStepVo> stepMap, Integer stepNo) {
        ApprovalFlowStepVo step = stepMap.get(stepNo);
        return step == null ? null : step.getStepName();
    }

    private Map<Integer, ApprovalFlowStepVo> resolveFrozenStepMap(ApprovalInstance instance) {
        Map<Integer, ApprovalFlowStepVo> result = new LinkedHashMap<>();
        Object steps = instance.getSnapshotJson() == null ? null : instance.getSnapshotJson().get("steps");
        if (steps instanceof List<?> frozenSteps) {
            for (Object item : frozenSteps) {
                if (!(item instanceof Map<?, ?> map)) {
                    continue;
                }
                Object stepNo = map.get("stepNo");
                Object stepName = map.get("stepName");
                if (!(stepNo instanceof Number) || stepName == null) {
                    continue;
                }
                ApprovalFlowStepVo stepVo = new ApprovalFlowStepVo();
                stepVo.setStepNo(((Number) stepNo).intValue());
                stepVo.setStepName(String.valueOf(stepName));
                Object assigneeType = map.get("assigneeType");
                Object assigneeTargetId = map.get("assigneeTargetId");
                Object assigneeTargetCode = map.get("assigneeTargetCode");
                Object assigneeTargetName = map.get("assigneeTargetName");
                if (assigneeType != null) {
                    stepVo.setAssigneeType(String.valueOf(assigneeType));
                }
                if (assigneeTargetId instanceof Number number) {
                    stepVo.setAssigneeTargetId(BigInteger.valueOf(number.longValue()));
                } else if (assigneeTargetId instanceof String string && StringUtils.hasText(string)) {
                    stepVo.setAssigneeTargetId(new BigInteger(string));
                }
                if (assigneeTargetCode != null) {
                    stepVo.setAssigneeTargetCode(String.valueOf(assigneeTargetCode));
                }
                if (assigneeTargetName != null) {
                    stepVo.setAssigneeTargetName(String.valueOf(assigneeTargetName));
                }
                result.put(stepVo.getStepNo(), stepVo);
            }
        }
        if (!result.isEmpty() && result.values().stream().allMatch(item -> item.getAssigneeType() != null && item.getAssigneeTargetId() != null)) {
            return result;
        }
        List<ApprovalFlowStep> storedSteps = approvalFlowStepMapper.selectListByQuery(
                QueryWrapper.create().eq(ApprovalFlowStep::getFlowId, instance.getFlowId()));
        for (ApprovalFlowStep step : storedSteps) {
            ApprovalFlowStepVo stepVo = result.computeIfAbsent(step.getStepNo(), key -> {
                ApprovalFlowStepVo value = new ApprovalFlowStepVo();
                value.setStepNo(step.getStepNo());
                return value;
            });
            if (!StringUtils.hasText(stepVo.getStepName())) {
                stepVo.setStepName(step.getStepName());
            }
            if (!StringUtils.hasText(stepVo.getAssigneeType())) {
                stepVo.setAssigneeType(step.getAssigneeType());
            }
            if (stepVo.getAssigneeTargetId() == null) {
                stepVo.setAssigneeTargetId(step.getAssigneeTargetId());
            }
            if (!StringUtils.hasText(stepVo.getAssigneeTargetCode())) {
                stepVo.setAssigneeTargetCode(step.getAssigneeTargetCode());
            }
            if (!StringUtils.hasText(stepVo.getAssigneeTargetName())) {
                stepVo.setAssigneeTargetName(step.getAssigneeTargetName());
            }
        }
        return result;
    }
}
