package tech.easyflow.approval.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.approval.entity.ApprovalFlow;
import tech.easyflow.approval.entity.ApprovalFlowScope;
import tech.easyflow.approval.entity.vo.ApprovalFlowDetailVo;
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
import tech.easyflow.approval.enums.ApprovalActionType;
import tech.easyflow.approval.enums.ApprovalFlowStatus;
import tech.easyflow.approval.enums.ApprovalResourceType;
import tech.easyflow.approval.enums.ApprovalScopeType;
import tech.easyflow.approval.mapper.ApprovalFlowMapper;
import tech.easyflow.approval.mapper.ApprovalFlowScopeMapper;
import tech.easyflow.approval.service.ApprovalFlowService;
import tech.easyflow.approval.service.ApprovalMatchService;
import tech.easyflow.system.entity.SysDept;
import tech.easyflow.system.service.SysDeptService;

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

/**
 * 审批流程匹配服务实现。
 */
@Service
public class ApprovalMatchServiceImpl implements ApprovalMatchService {

    @Resource
    private ApprovalFlowMapper approvalFlowMapper;

    @Resource
    private ApprovalFlowScopeMapper approvalFlowScopeMapper;

    @Resource
    private ApprovalFlowService approvalFlowService;

    @Resource
    private SysDeptService sysDeptService;

    /**
     * {@inheritDoc}
     */
    @Override
    public ApprovalFlowDetailVo matchFlow(ApprovalSubmitRequest request) {
        ApprovalFlowDetailVo matchedFlow = matchFlowOrNull(request);
        if (matchedFlow == null) {
            throw new BusinessException("当前资源上下文未命中审批流程");
        }
        return matchedFlow;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ApprovalFlowDetailVo matchFlowOrNull(ApprovalSubmitRequest request) {
        ApprovalSubmitRequest normalized = normalizeRequest(request);
        QueryWrapper flowWrapper = QueryWrapper.create()
                .eq(ApprovalFlow::getResourceType, normalized.getResourceType())
                .eq(ApprovalFlow::getActionType, normalized.getActionType())
                .eq(ApprovalFlow::getStatus, ApprovalFlowStatus.ENABLED.getCode());
        List<ApprovalFlow> flows = approvalFlowMapper.selectListByQuery(flowWrapper);
        if (CollectionUtil.isEmpty(flows)) {
            return null;
        }
        List<BigInteger> flowIds = flows.stream().map(ApprovalFlow::getId).collect(Collectors.toList());
        Map<BigInteger, List<ApprovalFlowScope>> scopeMap = approvalFlowScopeMapper.selectListByQuery(
                        QueryWrapper.create().in(ApprovalFlowScope::getFlowId, flowIds))
                .stream()
                .collect(Collectors.groupingBy(ApprovalFlowScope::getFlowId, LinkedHashMap::new, Collectors.toList()));

        List<MatchedFlow> matchedFlows = new ArrayList<>();
        for (ApprovalFlow flow : flows) {
            List<ApprovalFlowScope> scopes = scopeMap.getOrDefault(flow.getId(), List.of());
            if (matches(scopes, normalized)) {
                matchedFlows.add(new MatchedFlow(flow, computeSpecificity(scopes)));
            }
        }
        if (matchedFlows.isEmpty()) {
            return null;
        }

        matchedFlows.sort(Comparator
                .comparing((MatchedFlow item) -> item.flow.getPriority(), Comparator.nullsLast(Integer::compareTo)).reversed()
                .thenComparing(MatchedFlow::getSpecificity, Comparator.reverseOrder())
                .thenComparing(item -> item.flow.getCreated(), Comparator.nullsLast(Comparator.naturalOrder())));

        MatchedFlow first = matchedFlows.get(0);
        if (matchedFlows.size() > 1) {
            MatchedFlow second = matchedFlows.get(1);
            boolean samePriority = Objects.equals(first.flow.getPriority(), second.flow.getPriority());
            boolean sameSpecificity = Objects.equals(first.specificity, second.specificity);
            boolean sameCreated = Objects.equals(first.flow.getCreated(), second.flow.getCreated());
            if (samePriority && sameSpecificity && sameCreated) {
                throw new BusinessException("审批流程匹配冲突，请调整优先级或范围配置");
            }
        }
        return approvalFlowService.getFlowDetail(first.flow.getId());
    }

    private ApprovalSubmitRequest normalizeRequest(ApprovalSubmitRequest request) {
        if (request == null) {
            throw new BusinessException("审批请求不能为空");
        }
        if (request.getResourceId() == null) {
            throw new BusinessException("资源ID不能为空");
        }
        ApprovalSubmitRequest normalized = new ApprovalSubmitRequest();
        normalized.setResourceType(ApprovalResourceType.from(request.getResourceType()).getCode());
        normalized.setActionType(ApprovalActionType.from(request.getActionType()).getCode());
        normalized.setResourceId(request.getResourceId());
        normalized.setApplicantId(request.getApplicantId());
        normalized.setCategoryId(request.getCategoryId());
        normalized.setDeptId(request.getDeptId());
        normalized.setSummary(request.getSummary());
        normalized.setSnapshotJson(request.getSnapshotJson());
        return normalized;
    }

    private boolean matches(List<ApprovalFlowScope> scopes, ApprovalSubmitRequest request) {
        List<ApprovalFlowScope> categoryScopes = scopes.stream()
                .filter(item -> ApprovalScopeType.CATEGORY.getCode().equals(item.getScopeType()))
                .collect(Collectors.toList());
        List<ApprovalFlowScope> deptScopes = scopes.stream()
                .filter(item -> ApprovalScopeType.DEPT.getCode().equals(item.getScopeType()))
                .collect(Collectors.toList());
        return matchesCategory(categoryScopes, request.getCategoryId()) && matchesDept(deptScopes, request.getDeptId());
    }

    private boolean matchesCategory(List<ApprovalFlowScope> scopes, BigInteger categoryId) {
        if (CollectionUtil.isEmpty(scopes)) {
            return true;
        }
        if (categoryId == null) {
            return false;
        }
        return scopes.stream().anyMatch(item -> categoryId.equals(item.getScopeValue()));
    }

    private boolean matchesDept(List<ApprovalFlowScope> scopes, BigInteger deptId) {
        if (CollectionUtil.isEmpty(scopes)) {
            return true;
        }
        if (deptId == null) {
            return false;
        }
        SysDept dept = sysDeptService.getById(deptId);
        String ancestors = dept == null ? "" : dept.getAncestors();
        List<BigInteger> ancestorIds = new ArrayList<>();
        if (StringUtils.hasText(ancestors)) {
            for (String ancestor : ancestors.split(",")) {
                if (!StringUtils.hasText(ancestor) || "0".equals(ancestor.trim())) {
                    continue;
                }
                ancestorIds.add(new BigInteger(ancestor.trim()));
            }
        }
        return scopes.stream().anyMatch(item -> {
            if (deptId.equals(item.getScopeValue())) {
                return true;
            }
            return item.getIncludeChildren() != null
                    && item.getIncludeChildren() == 1
                    && ancestorIds.contains(item.getScopeValue());
        });
    }

    private int computeSpecificity(List<ApprovalFlowScope> scopes) {
        int score = scopes.size();
        boolean hasCategory = scopes.stream().anyMatch(item -> ApprovalScopeType.CATEGORY.getCode().equals(item.getScopeType()));
        boolean hasDept = scopes.stream().anyMatch(item -> ApprovalScopeType.DEPT.getCode().equals(item.getScopeType()));
        if (hasCategory) {
            score += 10;
        }
        if (hasDept) {
            score += 10;
        }
        return score;
    }

    private static class MatchedFlow {
        private final ApprovalFlow flow;
        private final Integer specificity;

        private MatchedFlow(ApprovalFlow flow, Integer specificity) {
            this.flow = flow;
            this.specificity = specificity;
        }

        public Integer getSpecificity() {
            return specificity;
        }
    }
}
