package cn.iocoder.yudao.module.bpm.service.oa;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.cash.BpmOACashCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.cash.BpmOACashRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.procure.BpmOAProcureListEditReqVO;
import cn.iocoder.yudao.framework.common.pojo.UploadUserFile;
import cn.iocoder.yudao.module.bpm.convert.oa.BpmOACashConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.financialpayment.FinancialPaymentDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.*;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOACashItemMapper;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOACashMapper;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAImprestMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.bpm.service.financialpayment.FinancialPaymentService;
import cn.iocoder.yudao.module.bpm.service.task.BpmHistoryProcessInstanceService;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.bank.BankApi;
import cn.iocoder.yudao.module.system.api.bank.dto.BankRespDTO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.OA_CASH_NOT_EXISTS;

/**
 * OA 现金支出 Service 实现类
 *
 * @author 符溶馨
 */
@Service
@Validated
public class BpmOACashServiceImpl extends BpmOABaseService implements BpmOACashService {

    /**
     * OA 现金支出对应的流程定义 KEY
     */
    public static final String PROCESS_KEY = "oa_cash";

    @Resource
    private BpmOACashMapper cashMapper;

    @Resource
    private BpmOACashItemMapper cashItemMapper;

    @Resource
    private BpmProcessInstanceApi processInstanceApi;

    @Resource
    @Lazy // 解决循环依赖
    private BpmProcessInstanceService bpmProcessInstanceService;

    @Resource
    private BpmOAProcureService bpmOAProcureService;
    @Resource
    private FinancialPaymentService financialPaymentService;

    @Resource
    private DeptApi deptApi;

    @Resource
    private BankApi bankApi;

    @Resource
    private AdminUserApi userApi;

    @Resource
    @Lazy // 解决循环依赖
    private BpmHistoryProcessInstanceService historyProcessInstanceService;

    @Resource
    private BpmOAImprestMapper bpmOAImprestMapper;

    @Resource
    private BpmOAImprestService bpmOAImprestService;


    @Override
    public Long createCash(Long userId, BpmOACashCreateReqVO createReqVO) {

        //插入OA 现金支出申请
        BpmOACashDO cash = BpmOACashConvert.INSTANCE.convert(createReqVO).setUserId(userId)
                .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
        cashMapper.insert(cash);

        // 批量插入现金支出明细数据
        List<BpmOACashItemDO> bpmOACashItemDOS = BeanUtils.toBean(createReqVO.getCashItem(), BpmOACashItemDO.class);
        bpmOACashItemDOS.forEach(item -> item.setCashId(cash.getId()));
        cashItemMapper.insertBatch(bpmOACashItemDOS);

        // 发起 BPM 流程
        Map<String, Object> processInstanceVariables = new HashMap<>();
        String processInstanceId = processInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY)
                        .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(cash.getId()))).getCheckedData();

        // 将工作流的编号，更新到 OA 现金支出单中
        cashMapper.updateById(new BpmOACashDO().setId(cash.getId()).setProcessInstanceId(processInstanceId));

        // 判断是否为重新发起的流程
        if (createReqVO.getProcessInstanceId() != null && createReqVO.getResult() == 3) {

            historyProcessInstanceService.createHistoryProcessInstance(processInstanceId, createReqVO.getProcessInstanceId());
        }

        //如果是备用金报销，则更新备用金流程status,并存入当前报销流程ID
        if (createReqVO.getImprestId() != null) {

            bpmOAImprestMapper.updateById(new BpmOAImprestDO().setId(createReqVO.getImprestId()).setStatus(BpmOAImprestDO.IN_PROGRESS));
        }

        List<Long> procureIds = new ArrayList<>();
        for (BpmOACashItemDO cashItemDO : bpmOACashItemDOS) {
            //报销类别为 采购费时
            if (cashItemDO.getType() == 4) {
                procureIds.add(cashItemDO.getProcureId());
            }
        }

        if (!CollectionUtil.isEmpty(procureIds)) {

            //所有关联的采购申请改为 进行中状态
            bpmOAProcureService.updatePayFlagByIds(
                    new BpmOAProcureListEditReqVO()
                            .setPayFlag(BpmOAProcureDO.IN_PROGRESS)
                            .setProcureIds(procureIds)
            );
        }

        List<UploadUserFile> fileItems = createReqVO.getFileItems();
        //这里的逻辑，如果fileItems不为空，且有数据，那么说明是上传了附件的，则需要更工作流文件表对应的实例Id
        if (fileItems != null && !fileItems.isEmpty()) {
            uploadBpmFileProcessInstanceId(processInstanceId, fileItems);
        }
        return cash.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCashResult(String processInstanceId, Long id, Integer result) {

        // 获得现金支出业务数据
        BpmOACashDO cash = validateLeaveExists(id);

        cashMapper.updateById(new BpmOACashDO().setId(id).setResult(result));

        // 获取现金支出明细
        List<BpmOACashItemDO> cashItemDOs = getCashItem(id);

        List<Long> procureIds = new ArrayList<>();
        //审核通过 （最后节点）
        if (BpmProcessInstanceResultEnum.APPROVE.getResult().equals(result)) {

            ProcessInstance instance = bpmProcessInstanceService.getProcessInstance(processInstanceId);

            if (instance.isEnded()) {
                //判断是否有采购报销
                for (BpmOACashItemDO cashItemDO : cashItemDOs) {
                    if (cashItemDO.getType() == 4) {
                        procureIds.add(cashItemDO.getProcureId());
                    }
                }
                //报销类别为 采购费时
                if (!CollectionUtil.isEmpty(procureIds)) {
                    //所有关联的采购申请改为 已支付状态
                    bpmOAProcureService.updatePayFlagByIds(
                            new BpmOAProcureListEditReqVO()
                                    .setPayFlag(BpmOAProcureDO.FLAG_TRUE)
                                    .setProcureIds(procureIds)
                    );
                }

                //判断是否为备用金报销
                int isImprest = 0;
                BigDecimal amount = null;
                if (cash.getImprestId() != null) {

                    // 获取备用金详情
                    BpmOAImprestDO bpmOAImprestDO = bpmOAImprestService.getImprest(cash.getImprestId());

                    BpmOAImprestDO updateVO = new BpmOAImprestDO()
                            .setId(cash.getImprestId())
                            .setReimbursedAmount(bpmOAImprestDO.getReimbursedAmount().add(cash.getTotalMoney()));

                    // 获取 剩余备用金报销金额
                    amount = bpmOAImprestDO.getAmount().subtract(updateVO.getReimbursedAmount());
                    if (amount.compareTo(BigDecimal.ZERO) <= 0) {

                        //将相应备用金申请状态改为 已报销
                        bpmOAImprestMapper.updateById(updateVO.setStatus(BpmOAImprestDO.FLAG_TRUE));

                        if (amount.compareTo(BigDecimal.ZERO) < 0) {
                            isImprest = 1;
                        }else {
                            isImprest = 2;
                        }
                    }else {
                        //将相应备用金申请状态改为 未报销完毕
                        bpmOAImprestMapper.updateById(updateVO.setStatus(BpmOAImprestDO.NOT_COMPLETED));
                        isImprest = 2;
                    }
                }

                String reason = cashItemDOs.stream().map(BpmOACashItemDO::getReason).filter(StrUtil::isNotEmpty).collect(Collectors.joining(","));

                BpmProcessInstanceExtDO processInstance = bpmProcessInstanceService.getProcessInstanceDO(processInstanceId);
                CommonResult<AdminUserRespDTO> user = userApi.getUser(cash.getUserId());

                // 从缓存中部门所属公司信息
                DeptRespDTO deptRespDTO = deptApi.getCompanyByDept(cashItemDOs.get(0).getDeptId()).getCheckedData();

                // 获取收款人信息
                BankRespDTO bankRespDTO = new BankRespDTO();
                if (cash.getBankId() != null) {
                    bankRespDTO = bankApi.getBank(cash.getBankId()).getCheckedData();
                }

                // -- 插入到财务支付表中
                financialPaymentService.save(new FinancialPaymentDO()
                        .setUserId(cash.getUserId())
                        .setDeptId(user.getData() == null ? null : user.getData().getDeptId())
                        .setProcessInstanceId(cash.getProcessInstanceId())
                        .setProcessInstanceName(processInstance.getName())
                        .setReason(reason)
                        .setObjectId(id)
                        .setType(1)
                        .setStatus(isImprest == 2 ? 4 : 0)
                        .setAmountPayable(isImprest == 1 ? amount.abs() : cash.getTotalMoney())
                        .setBeginTime(processInstance.getCreateTime())
                        .setEndTime(processInstance.getEndTime())
                        .setRecipientName(bankRespDTO != null ? bankRespDTO.getNickname() + "," + bankRespDTO.getBankNo() + "," + bankRespDTO.getBankName() : "")
                        .setCompanyId(deptRespDTO != null ? deptRespDTO.getId() : null)
                );
            }
        }

        // -- 自己取消
        // -- 审核拒绝
        //所有关联的采购申请改为 未支付状态
        if (BpmProcessInstanceResultEnum.REJECT.getResult().equals(result)
                || BpmProcessInstanceResultEnum.CANCEL.getResult().equals(result)
                || BpmProcessInstanceResultEnum.BACK.getResult().equals(result)) {

            if (cash.getImprestId() != null) {

                Long count = cashMapper.selectCount(new LambdaQueryWrapperX<BpmOACashDO>()
                        .eq(BpmOACashDO::getImprestId, cash.getImprestId())
                        .eq(BpmOACashDO::getResult, BpmProcessInstanceResultEnum.APPROVE.getResult()));
                if (count > 0L) {
                    //将相应备用金申请状态改为 未报销
                    bpmOAImprestMapper.updateById(new BpmOAImprestDO().setId(cash.getImprestId()).setStatus(BpmOAImprestDO.NOT_COMPLETED));
                }else {
                    bpmOAImprestMapper.updateById(new BpmOAImprestDO().setId(cash.getImprestId()).setStatus(BpmOAImprestDO.FLAG_FALSE));
                }
            }

            //判断是否有采购报销
            for (BpmOACashItemDO cashItemDO : cashItemDOs) {

                if (cashItemDO.getType() == 4) {
                    procureIds.add(cashItemDO.getProcureId());
                }
            }
            //报销类别为 采购费时
            if (!CollectionUtil.isEmpty(procureIds)) {

                //所有关联的采购申请改为 未支付状态
                bpmOAProcureService.updatePayFlagByIds(
                        new BpmOAProcureListEditReqVO()
                                .setPayFlag(BpmOAProcureDO.FLAG_FALSE)
                                .setProcureIds(procureIds)
                );
            }
        }
    }

    private BpmOACashDO validateLeaveExists(Long id) {
        BpmOACashDO cash = cashMapper.selectById(id);
        if (cash == null) {
            throw exception(OA_CASH_NOT_EXISTS);
        }

        return cash;
    }

    @Override
    public BpmOACashDO getCash(Long id) {

        return cashMapper.selectById(id);
    }

    @Override
    public List<BpmOACashItemDO> getCashItem(Long cashId) {

        return cashItemMapper.selectList(BpmOACashItemDO::getCashId, cashId);
    }

    @Override
    public BpmOACashDO getByProcessInstanceId(String processInstanceId) {
        List<BpmOACashDO> list = cashMapper.selectList(
                new LambdaQueryWrapper<BpmOACashDO>()
                        .eq(BpmOACashDO::getProcessInstanceId, processInstanceId));
        if (!list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }

    @Override
    public BpmOACashRespVO convertCash(BpmOACashDO cash) {

        // 获取明细数据
        List<BpmOACashItemDO> cashItemDOs = getCashItem(cash.getId());

        //获取部门信息map
        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(cashItemDOs, BpmOACashItemDO::getDeptId));

        // 获取银行卡信息
        BankRespDTO bankRespDTO = null;
        if (cash.getBankId() != null) {
            bankRespDTO = bankApi.getBank(cash.getBankId()).getCheckedData();
        }

        return BpmOACashConvert.INSTANCE.convert1(cash, cashItemDOs, deptMap, bankRespDTO);
    }
}
