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.approval.entity.ApprovalTask;
import tech.easyflow.approval.entity.vo.ApprovalAssigneeOptionVo;
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
import tech.easyflow.approval.enums.ApprovalAssigneeType;
import tech.easyflow.approval.enums.ApprovalTaskStatus;
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
import tech.easyflow.approval.service.ApprovalAssigneeService;
import tech.easyflow.common.constant.enums.EnumDataStatus;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.entity.SysRole;
import tech.easyflow.system.service.SysAccountService;
import tech.easyflow.system.service.SysRoleService;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 审批对象服务实现。
 */
@Service
public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {

    @Resource
    private SysRoleService sysRoleService;

    @Resource
    private SysAccountService sysAccountService;

    @Resource
    private ApprovalTaskMapper approvalTaskMapper;

    /**
     * {@inheritDoc}
     */
    @Override
    public ApprovalFlowStepVo normalizeStepAssignee(ApprovalFlowStepVo step) {
        if (step == null) {
            throw new BusinessException("审批步骤不能为空");
        }
        ApprovalAssigneeType assigneeType = ApprovalAssigneeType.from(step.getAssigneeType());
        BigInteger targetId = step.getAssigneeTargetId();
        if (targetId == null) {
            throw new BusinessException("审批对象不能为空");
        }
        ApprovalAssigneeOptionVo option = resolveAssigneeOption(assigneeType, targetId);
        step.setAssigneeType(assigneeType.getCode());
        step.setAssigneeTargetId(option.getId());
        step.setAssigneeTargetCode(option.getCode());
        step.setAssigneeTargetName(option.getName());
        return step;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<ApprovalAssigneeOptionVo> listRoleOptions() {
        return sysRoleService.list(QueryWrapper.create()
                        .eq(SysRole::getStatus, EnumDataStatus.AVAILABLE.getCode())
                        .orderBy("role_name asc, id asc"))
                .stream()
                .map(this::toRoleOption)
                .collect(Collectors.toList());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Page<ApprovalAssigneeOptionVo> pageAccountOptions(String keyword, Long pageNumber, Long pageSize) {
        long actualPageNumber = pageNumber == null || pageNumber < 1 ? 1L : pageNumber;
        long actualPageSize = pageSize == null || pageSize < 1 ? 20L : pageSize;
        QueryWrapper queryWrapper = QueryWrapper.create()
                .eq(SysAccount::getStatus, EnumDataStatus.AVAILABLE.getCode())
                .orderBy("nickname asc, login_name asc, id asc");
        if (StringUtils.hasText(keyword)) {
            String likeKeyword = "%" + keyword.trim() + "%";
            queryWrapper.and("(`login_name` like ? or `nickname` like ?)", likeKeyword, likeKeyword);
        }
        Page<SysAccount> page = sysAccountService.page(new Page<>(actualPageNumber, actualPageSize), queryWrapper);
        Page<ApprovalAssigneeOptionVo> result = new Page<>(actualPageNumber, actualPageSize, page.getTotalRow());
        result.setRecords(page.getRecords().stream().map(this::toAccountOption).collect(Collectors.toList()));
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<BigInteger> getAvailableRoleIds(BigInteger accountId) {
        if (accountId == null) {
            return Set.of();
        }
        return sysRoleService.getRolesByAccountId(accountId).stream()
                .filter(role -> role != null && EnumDataStatus.AVAILABLE.getCode().equals(role.getStatus()))
                .map(SysRole::getId)
                .collect(Collectors.toCollection(LinkedHashSet::new));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean canHandleTask(ApprovalTask task, BigInteger operatorId, Set<BigInteger> roleIds) {
        if (task == null || operatorId == null) {
            return false;
        }
        ApprovalAssigneeType assigneeType = ApprovalAssigneeType.from(task.getAssigneeType());
        if (ApprovalAssigneeType.USER == assigneeType) {
            return operatorId.equals(task.getAssigneeTargetId());
        }
        return roleIds != null && roleIds.contains(task.getAssigneeTargetId());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<BigInteger> listPendingInstanceIds(BigInteger operatorId, Set<BigInteger> roleIds, List<BigInteger> instanceIds) {
        if (operatorId == null) {
            return Set.of();
        }
        Set<BigInteger> result = new LinkedHashSet<>();
        QueryWrapper userQuery = QueryWrapper.create()
                .eq(ApprovalTask::getStatus, ApprovalTaskStatus.PENDING.getCode());
        if (CollectionUtil.isNotEmpty(instanceIds)) {
            userQuery.in(ApprovalTask::getInstanceId, instanceIds);
        }
        userQuery.eq(ApprovalTask::getAssigneeType, ApprovalAssigneeType.USER.getCode())
                .eq(ApprovalTask::getAssigneeTargetId, operatorId);
        result.addAll(approvalTaskMapper.selectListByQuery(userQuery).stream()
                .map(ApprovalTask::getInstanceId)
                .collect(Collectors.toCollection(LinkedHashSet::new)));
        if (CollectionUtil.isNotEmpty(roleIds)) {
            QueryWrapper roleQuery = QueryWrapper.create()
                    .eq(ApprovalTask::getStatus, ApprovalTaskStatus.PENDING.getCode())
                    .eq(ApprovalTask::getAssigneeType, ApprovalAssigneeType.ROLE.getCode())
                    .in(ApprovalTask::getAssigneeTargetId, roleIds);
            if (CollectionUtil.isNotEmpty(instanceIds)) {
                roleQuery.in(ApprovalTask::getInstanceId, instanceIds);
            }
            result.addAll(approvalTaskMapper.selectListByQuery(roleQuery).stream()
                    .map(ApprovalTask::getInstanceId)
                    .collect(Collectors.toCollection(LinkedHashSet::new)));
        }
        return result;
    }

    private ApprovalAssigneeOptionVo resolveAssigneeOption(ApprovalAssigneeType assigneeType, BigInteger targetId) {
        if (ApprovalAssigneeType.ROLE == assigneeType) {
            SysRole role = sysRoleService.getById(targetId);
            if (role == null || !EnumDataStatus.AVAILABLE.getCode().equals(role.getStatus())) {
                throw new BusinessException("审批角色不存在或未启用");
            }
            return toRoleOption(role);
        }
        SysAccount account = sysAccountService.getById(targetId);
        if (account == null || !EnumDataStatus.AVAILABLE.getCode().equals(account.getStatus())) {
            throw new BusinessException("审批用户不存在或未启用");
        }
        return toAccountOption(account);
    }

    private ApprovalAssigneeOptionVo toRoleOption(SysRole role) {
        ApprovalAssigneeOptionVo option = new ApprovalAssigneeOptionVo();
        option.setId(role.getId());
        option.setCode(role.getRoleKey());
        option.setName(role.getRoleName());
        return option;
    }

    private ApprovalAssigneeOptionVo toAccountOption(SysAccount account) {
        ApprovalAssigneeOptionVo option = new ApprovalAssigneeOptionVo();
        option.setId(account.getId());
        option.setCode(account.getLoginName());
        option.setName(resolveAccountDisplayName(account));
        return option;
    }

    private String resolveAccountDisplayName(SysAccount account) {
        if (StringUtils.hasText(account.getNickname())) {
            return account.getNickname().trim();
        }
        if (StringUtils.hasText(account.getLoginName())) {
            return account.getLoginName().trim();
        }
        return String.valueOf(account.getId());
    }
}
