package tech.easyflow.approval.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.approval.entity.ApprovalInstance;
import tech.easyflow.approval.entity.ApprovalFlowStep;
import tech.easyflow.approval.entity.ApprovalLog;
import tech.easyflow.approval.entity.ApprovalTask;
import tech.easyflow.approval.entity.vo.ApprovalFlowDetailVo;
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
import tech.easyflow.approval.service.ApprovalActionFacade;
import tech.easyflow.approval.service.ApprovalAssigneeService;
import tech.easyflow.approval.enums.ApprovalAssigneeType;
import tech.easyflow.approval.enums.ApprovalEventType;
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
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.ApprovalInstanceService;
import tech.easyflow.approval.service.ApprovalMatchService;

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

/**
 * 审批实例服务实现。
 */
@Service
public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {

    @Resource
    private ApprovalMatchService approvalMatchService;

    @Resource
    private ApprovalInstanceMapper approvalInstanceMapper;

    @Resource
    private ApprovalTaskMapper approvalTaskMapper;

    @Resource
    private ApprovalLogMapper approvalLogMapper;

    @Resource
    private ApprovalFlowStepMapper approvalFlowStepMapper;

    @Resource
    private ApprovalAssigneeService approvalAssigneeService;

    @Lazy
    @Resource
    private ApprovalActionFacade approvalActionFacade;

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public BigInteger submitApproval(ApprovalSubmitRequest request) {
        ApprovalFlowDetailVo flow = approvalMatchService.matchFlow(request);
        if (CollectionUtil.isEmpty(flow.getSteps())) {
            throw new BusinessException("审批流程未配置步骤");
        }
        if (request.getApplicantId() == null) {
            throw new BusinessException("申请人不能为空");
        }

        List<ApprovalFlowStepVo> steps = new ArrayList<>(flow.getSteps());
        steps.sort(Comparator.comparing(ApprovalFlowStepVo::getStepNo));
        ApprovalFlowStepVo firstStep = steps.get(0);
        Date now = new Date();

        ApprovalInstance instance = new ApprovalInstance();
        instance.setFlowId(flow.getId());
        instance.setFlowVersion(flow.getVersion());
        instance.setResourceType(flow.getResourceType());
        instance.setResourceId(request.getResourceId());
        instance.setActionType(flow.getActionType());
        instance.setStatus(ApprovalInstanceStatus.PENDING.getCode());
        instance.setCurrentStepNo(firstStep.getStepNo());
        instance.setSnapshotJson(buildInstanceSnapshot(request, flow, steps));
        instance.setSummary(request.getSummary());
        instance.setApplicantId(request.getApplicantId());
        instance.setSubmittedAt(now);
        instance.setCreated(now);
        instance.setCreatedBy(request.getApplicantId());
        instance.setModified(now);
        instance.setModifiedBy(request.getApplicantId());
        approvalInstanceMapper.insert(instance);

        createTask(instance.getId(), firstStep, request.getApplicantId(), now);
        appendLog(instance.getId(), ApprovalEventType.SUBMITTED.getCode(), request.getApplicantId(), Map.of(
                "flowId", flow.getId(),
                "flowVersion", flow.getVersion(),
                "summary", request.getSummary()
        ), now);
        appendLog(instance.getId(), ApprovalEventType.STEP_CREATED.getCode(), request.getApplicantId(),
                buildStepCreatedPayload(firstStep), now);
        return instance.getId();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approve(BigInteger instanceId, String comment, BigInteger operatorId) {
        ApprovalInstance instance = requireActiveInstance(instanceId);
        ApprovalTask currentTask = requireCurrentTask(instanceId, instance.getCurrentStepNo());
        assertTaskOperable(currentTask, operatorId);
        List<ApprovalFlowStepVo> steps = resolveFrozenSteps(instance);

        Date now = new Date();
        finishTask(currentTask, ApprovalTaskStatus.APPROVED.getCode(), comment, operatorId, now);
        int currentIndex = findCurrentStepIndex(steps, instance.getCurrentStepNo());
        if (currentIndex == steps.size() - 1) {
            instance.setStatus(ApprovalInstanceStatus.APPROVED.getCode());
            instance.setFinishedAt(now);
        } else {
            ApprovalFlowStepVo nextStep = steps.get(currentIndex + 1);
            instance.setStatus(ApprovalInstanceStatus.PROCESSING.getCode());
            instance.setCurrentStepNo(nextStep.getStepNo());
            createTask(instance.getId(), nextStep, operatorId, now);
            appendLog(instance.getId(), ApprovalEventType.STEP_CREATED.getCode(), operatorId,
                    buildStepCreatedPayload(nextStep), now);
        }
        instance.setModified(now);
        instance.setModifiedBy(operatorId);
        approvalInstanceMapper.update(instance);
        appendLog(instance.getId(), ApprovalEventType.APPROVED.getCode(), operatorId, Map.of(
                "stepNo", currentTask.getStepNo(),
                "comment", comment == null ? "" : comment
        ), now);
        if (ApprovalInstanceStatus.from(instance.getStatus()).isFinished()) {
            approvalActionFacade.handleApproved(instance, operatorId, comment);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reject(BigInteger instanceId, String comment, BigInteger operatorId) {
        ApprovalInstance instance = requireActiveInstance(instanceId);
        ApprovalTask currentTask = requireCurrentTask(instanceId, instance.getCurrentStepNo());
        assertTaskOperable(currentTask, operatorId);
        Date now = new Date();
        finishTask(currentTask, ApprovalTaskStatus.REJECTED.getCode(), comment, operatorId, now);
        instance.setStatus(ApprovalInstanceStatus.REJECTED.getCode());
        instance.setFinishedAt(now);
        instance.setModified(now);
        instance.setModifiedBy(operatorId);
        approvalInstanceMapper.update(instance);
        appendLog(instance.getId(), ApprovalEventType.REJECTED.getCode(), operatorId, Map.of(
                "stepNo", currentTask.getStepNo(),
                "comment", comment == null ? "" : comment
        ), now);
        approvalActionFacade.handleRejected(instance, operatorId, comment);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revoke(BigInteger instanceId, String comment, BigInteger operatorId) {
        ApprovalInstance instance = requireActiveInstance(instanceId);
        ApprovalTask currentTask = requireCurrentTask(instanceId, instance.getCurrentStepNo());
        assertTaskOperable(currentTask, operatorId);
        Date now = new Date();
        finishTask(currentTask, ApprovalTaskStatus.REVOKED.getCode(), comment, operatorId, now);
        instance.setStatus(ApprovalInstanceStatus.REVOKED.getCode());
        instance.setFinishedAt(now);
        instance.setModified(now);
        instance.setModifiedBy(operatorId);
        approvalInstanceMapper.update(instance);
        appendLog(instance.getId(), ApprovalEventType.REVOKED.getCode(), operatorId, Map.of(
                "stepNo", currentTask.getStepNo(),
                "comment", comment == null ? "" : comment
        ), now);
        approvalActionFacade.handleRevoked(instance, operatorId, comment);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean existsActiveInstance(String resourceType, BigInteger resourceId) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .eq(ApprovalInstance::getResourceType, resourceType)
                .eq(ApprovalInstance::getResourceId, resourceId)
                .notIn(ApprovalInstance::getStatus,
                        ApprovalInstanceStatus.APPROVED.getCode(),
                        ApprovalInstanceStatus.REJECTED.getCode(),
                        ApprovalInstanceStatus.REVOKED.getCode());
        return approvalInstanceMapper.selectCountByQuery(queryWrapper) > 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ApprovalInstance getById(BigInteger instanceId) {
        return approvalInstanceMapper.selectOneById(instanceId);
    }

    private Map<String, Object> buildInstanceSnapshot(ApprovalSubmitRequest request, ApprovalFlowDetailVo flow,
                                                      List<ApprovalFlowStepVo> steps) {
        Map<String, Object> snapshot = new LinkedHashMap<>();
        if (request.getSnapshotJson() != null) {
            snapshot.putAll(request.getSnapshotJson());
        }
        snapshot.put("categoryId", request.getCategoryId());
        snapshot.put("deptId", request.getDeptId());
        snapshot.put("flowId", flow.getId());
        snapshot.put("flowVersion", flow.getVersion());
        snapshot.put("steps", steps.stream().map(item -> {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("stepNo", item.getStepNo());
            map.put("stepName", item.getStepName());
            map.put("assigneeType", item.getAssigneeType());
            map.put("assigneeTargetId", item.getAssigneeTargetId());
            map.put("assigneeTargetCode", item.getAssigneeTargetCode());
            map.put("assigneeTargetName", item.getAssigneeTargetName());
            return map;
        }).collect(Collectors.toList()));
        return snapshot;
    }

    private List<ApprovalFlowStepVo> resolveFrozenSteps(ApprovalInstance instance) {
        if (instance.getSnapshotJson() == null) {
            throw new BusinessException("审批实例缺少流程快照");
        }
        Object value = instance.getSnapshotJson().get("steps");
        if (!(value instanceof List<?> steps)) {
            throw new BusinessException("审批实例缺少冻结步骤数据");
        }
        List<ApprovalFlowStepVo> result = new ArrayList<>();
        for (Object step : steps) {
            if (!(step instanceof Map<?, ?> stepMap)) {
                continue;
            }
            Object stepNo = stepMap.get("stepNo");
            Object stepName = stepMap.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 = stepMap.get("assigneeType");
            Object assigneeTargetId = stepMap.get("assigneeTargetId");
            Object assigneeTargetCode = stepMap.get("assigneeTargetCode");
            Object assigneeTargetName = stepMap.get("assigneeTargetName");
            if (assigneeType instanceof String type) {
                stepVo.setAssigneeType(type);
            }
            if (assigneeTargetId instanceof Number number) {
                stepVo.setAssigneeTargetId(BigInteger.valueOf(number.longValue()));
            } else if (assigneeTargetId instanceof String string && !string.isBlank()) {
                stepVo.setAssigneeTargetId(new BigInteger(string));
            }
            if (assigneeTargetCode != null) {
                stepVo.setAssigneeTargetCode(String.valueOf(assigneeTargetCode));
            }
            if (assigneeTargetName != null) {
                stepVo.setAssigneeTargetName(String.valueOf(assigneeTargetName));
            }
            result.add(stepVo);
        }
        mergeStepAssigneeFromFlow(instance.getFlowId(), result);
        result.sort(Comparator.comparing(ApprovalFlowStepVo::getStepNo));
        if (result.isEmpty()) {
            throw new BusinessException("审批实例缺少冻结步骤数据");
        }
        return result;
    }

    private void createTask(BigInteger instanceId, ApprovalFlowStepVo step, BigInteger operatorId, Date now) {
        ApprovalTask task = new ApprovalTask();
        task.setInstanceId(instanceId);
        task.setStepNo(step.getStepNo());
        task.setStatus(ApprovalTaskStatus.PENDING.getCode());
        task.setAssigneeRoleCode(ApprovalAssigneeType.ROLE.getCode().equals(step.getAssigneeType())
                ? step.getAssigneeTargetCode()
                : null);
        task.setAssigneeType(step.getAssigneeType());
        task.setAssigneeTargetId(step.getAssigneeTargetId());
        task.setAssigneeTargetCode(step.getAssigneeTargetCode());
        task.setAssigneeTargetName(step.getAssigneeTargetName());
        task.setCreated(now);
        task.setCreatedBy(operatorId);
        task.setModified(now);
        task.setModifiedBy(operatorId);
        approvalTaskMapper.insert(task);
    }

    private void finishTask(ApprovalTask task, String status, String comment, BigInteger operatorId, Date now) {
        task.setStatus(status);
        task.setComment(comment);
        task.setActedBy(operatorId);
        task.setActedAt(now);
        task.setModified(now);
        task.setModifiedBy(operatorId);
        approvalTaskMapper.update(task);
    }

    private void appendLog(BigInteger instanceId, String eventType, BigInteger operatorId, Map<String, Object> payload, Date now) {
        ApprovalLog log = new ApprovalLog();
        log.setInstanceId(instanceId);
        log.setEventType(eventType);
        log.setOperatorId(operatorId);
        log.setPayloadJson(new LinkedHashMap<>(payload));
        log.setCreated(now);
        log.setCreatedBy(operatorId);
        log.setModified(now);
        log.setModifiedBy(operatorId);
        approvalLogMapper.insert(log);
    }

    private ApprovalInstance requireActiveInstance(BigInteger instanceId) {
        if (instanceId == null) {
            throw new BusinessException("审批实例ID不能为空");
        }
        ApprovalInstance instance = approvalInstanceMapper.selectOneById(instanceId);
        if (instance == null) {
            throw new BusinessException("审批实例不存在");
        }
        if (ApprovalInstanceStatus.from(instance.getStatus()).isFinished()) {
            throw new BusinessException("审批实例已结束，无法继续处理");
        }
        return instance;
    }

    private ApprovalTask requireCurrentTask(BigInteger instanceId, Integer stepNo) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .eq(ApprovalTask::getInstanceId, instanceId)
                .eq(ApprovalTask::getStepNo, stepNo)
                .eq(ApprovalTask::getStatus, ApprovalTaskStatus.PENDING.getCode());
        ApprovalTask task = approvalTaskMapper.selectOneByQuery(queryWrapper);
        if (task == null) {
            throw new BusinessException("当前审批任务不存在");
        }
        return task;
    }

    private int findCurrentStepIndex(List<ApprovalFlowStepVo> steps, Integer currentStepNo) {
        for (int i = 0; i < steps.size(); i++) {
            if (steps.get(i).getStepNo().equals(currentStepNo)) {
                return i;
            }
        }
        throw new BusinessException("审批流程步骤不存在");
    }

    /**
     * 校验当前操作人是否命中审批任务。
     *
     * @param task 审批任务
     * @param operatorId 当前操作人
     */
    private void assertTaskOperable(ApprovalTask task, BigInteger operatorId) {
        if (!approvalAssigneeService.canHandleTask(task, operatorId, approvalAssigneeService.getAvailableRoleIds(operatorId))) {
            throw new BusinessException("当前用户无权处理该审批任务");
        }
    }

    /**
     * 为历史审批实例补齐流程步骤里的审批对象信息。
     *
     * @param flowId 流程ID
     * @param steps 冻结步骤
     */
    private void mergeStepAssigneeFromFlow(BigInteger flowId, List<ApprovalFlowStepVo> steps) {
        List<ApprovalFlowStep> storedSteps = approvalFlowStepMapper.selectListByQuery(
                QueryWrapper.create().eq(ApprovalFlowStep::getFlowId, flowId));
        if (CollectionUtil.isEmpty(storedSteps)) {
            return;
        }
        Map<Integer, ApprovalFlowStep> storedMap = storedSteps.stream().collect(Collectors.toMap(
                ApprovalFlowStep::getStepNo,
                item -> item,
                (left, right) -> left,
                LinkedHashMap::new
        ));
        for (ApprovalFlowStepVo step : steps) {
            if (step.getAssigneeTargetId() != null && step.getAssigneeType() != null) {
                continue;
            }
            ApprovalFlowStep storedStep = storedMap.get(step.getStepNo());
            if (storedStep == null) {
                continue;
            }
            step.setAssigneeType(storedStep.getAssigneeType());
            step.setAssigneeTargetId(storedStep.getAssigneeTargetId());
            step.setAssigneeTargetCode(storedStep.getAssigneeTargetCode());
            step.setAssigneeTargetName(storedStep.getAssigneeTargetName());
        }
    }

    /**
     * 组装步骤创建日志载荷。
     *
     * @param step 步骤信息
     * @return 日志载荷
     */
    private Map<String, Object> buildStepCreatedPayload(ApprovalFlowStepVo step) {
        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("stepNo", step.getStepNo());
        payload.put("stepName", step.getStepName());
        payload.put("assigneeType", step.getAssigneeType());
        payload.put("assigneeTargetId", step.getAssigneeTargetId());
        payload.put("assigneeTargetCode", step.getAssigneeTargetCode());
        payload.put("assigneeTargetName", step.getAssigneeTargetName());
        return payload;
    }
}
