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

import cn.iocoder.yudao.framework.common.pojo.UploadUserFile;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
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.refund.BpmOARefundCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.refund.BpmOARefundItems;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOARefundDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOARefundMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
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.rental.RentalDepositRecordApi;
import cn.iocoder.yudao.module.system.api.rental.RentalItemsRecordApi;
import cn.iocoder.yudao.module.system.api.rental.RentalOrderApi;
import cn.iocoder.yudao.module.system.api.rental.dto.RentalItemsRecordDTO;
import cn.iocoder.yudao.module.system.api.rental.dto.RentalOrderDTO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

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

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.OA_REFUND_NOT_EXISTS;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.RENTAL_REFUND_AMOUNT_EXCESS;

/**
 * OA 退款申请 Service 实现类
 *
 * @author 符溶馨

 */
@Service
@Validated
public class BpmOARefundServiceImpl extends BpmOABaseService implements BpmOARefundService{

    /**
     * OA 退款对应的流程定义 KEY
     */
    public static final String PROCESS_KEY = "oa_refund_2";

    @Resource
    private BpmOARefundMapper refundMapper;

    @Resource
    private BpmProcessInstanceApi processInstanceApi;

    @Resource
    private BpmHistoryProcessInstanceService historyProcessInstanceService;

    @Resource
    private BpmProcessInstanceService bpmProcessInstanceService;

    @Resource
    private RentalOrderApi rentalOrderApi;

    @Resource
    private RentalItemsRecordApi rentalItemsRecordApi;

    @Resource
    private RentalDepositRecordApi depositRecordApi;

    @Override
    public Long createRefund(Long userId, BpmOARefundCreateReqVO createReqVO) {

        // 发起流程前 校验扣款金额+退款金额是否大于已收金额
        validateRefundAmount(createReqVO.getOrderNo(), createReqVO.getChargebacksAmount(), createReqVO.getRefundAmount());

        //插入OA 退款申请
        BpmOARefundDO refund = BeanUtils.toBean(createReqVO, BpmOARefundDO.class)
                .setUserId(userId)
                .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
        refundMapper.insert(refund) ;

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

        // 将工作流的编号，更新到 OA 退款申请单中
        refundMapper.updateById(new BpmOARefundDO().setId(refund.getId()).setProcessInstanceId(processInstanceId));

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

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

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

        // 退款申请发起后，变更租赁订单状态为 退款申请中
        rentalOrderApi.updateOrder(new RentalOrderDTO()
                .setOrderNo(refund.getOrderNo())
                .setStatus(2));

        return refund.getId();
    }

    /**
     * 校验扣款金额+退款金额是否大于已收金额
     */
    private void validateRefundAmount(String orderNo, BigDecimal chargebacksAmount, BigDecimal refundAmount) {

        // 获取订单的已收金额 和 已退金额
        Map<String, BigDecimal> respVO = depositRecordApi.getRecordAmount(orderNo).getCheckedData();
        // 获取当前订单 还需退款金额 = 已收金额 - 已退金额
        BigDecimal recordAmount = respVO.get("receivedAmount").subtract(respVO.get("refundAmount"));

        if (recordAmount.compareTo(chargebacksAmount.add(refundAmount)) < 0) {
            throw exception(RENTAL_REFUND_AMOUNT_EXCESS);
        }
    }

    @Override
    public void updateRefundResult(String processInstanceId, Long id, Integer result) {

        BpmOARefundDO refundDO = validateLeaveExists(id);
        //审核通过 （最后节点）
        if (BpmProcessInstanceResultEnum.APPROVE.getResult().equals(result)) {

            ProcessInstance instance = bpmProcessInstanceService.getProcessInstance(processInstanceId);

            if (instance.isEnded()) {

                // 获取订单的扣款金额
                BigDecimal chargebacks = rentalOrderApi.getOrder(refundDO.getOrderNo()).getCheckedData().getChargebacksAmount();

                // 审批通过后 更新租赁订单信息
                rentalOrderApi.updateOrder(new RentalOrderDTO()
                        .setOrderNo(refundDO.getOrderNo())
                        .setChargebacksAmount(refundDO.getChargebacksAmount().add(chargebacks))  // 扣款金额累加
                        .setStatus(3)); // 状态变更为 等待退款中

                List<BpmOARefundItems> items = refundDO.getRefundItems();
                //直接从数据库取出来的List<Reimbursement> 实际上是List<LinkedHashMap>类型 所以不能直接遍历
                //将list再次转为json串，然后由json串再转为list
                String json = JsonUtils.toJsonString(items);
                items = JsonUtils.parseArray(json, BpmOARefundItems.class);

                // 审批通过后 更新租赁物品 归还信息
                List<RentalItemsRecordDTO> createReqVO = items.stream()
                        .map(item -> new RentalItemsRecordDTO()
                                .setOrderNo(refundDO.getOrderNo())
                                .setRentalItemsType(item.getRentalItemsType())
                                .setNumber(item.getRentalNumber())
                                .setType(2))
                        .collect(Collectors.toList());
                rentalItemsRecordApi.create(createReqVO);
            }
        }

        // -- 自己取消
        // -- 审核拒绝
        if (BpmProcessInstanceResultEnum.REJECT.getResult().equals(result)
                || BpmProcessInstanceResultEnum.CANCEL.getResult().equals(result)
                || BpmProcessInstanceResultEnum.BACK.getResult().equals(result)) {

            // 变更租赁订单状态 为租借中
            rentalOrderApi.updateOrder(new RentalOrderDTO()
                    .setOrderNo(refundDO.getOrderNo())
                    .setStatus(1));
        }

        refundMapper.updateById(new BpmOARefundDO().setId(id).setResult(result));
    }

    private BpmOARefundDO validateLeaveExists(Long id) {
        BpmOARefundDO refundDO = refundMapper.selectById(id);
        if (refundDO == null) {
            throw exception(OA_REFUND_NOT_EXISTS);
        }
        return refundDO;
    }

    @Override
    public BpmOARefundDO getRefund(Long id) {

        return refundMapper.selectById(id);
    }

    @Override
    public BpmOARefundDO getByProcessInstanceId(String processInstanceId) {

        return refundMapper.selectOne(BpmOARefundDO::getProcessInstanceId, processInstanceId);
    }

    @Override
    public List<BpmOARefundDO> getListByOrderNo(List<String> orderNos) {
        return refundMapper.selectList(new LambdaQueryWrapperX<BpmOARefundDO>()
                .inIfPresent(BpmOARefundDO::getOrderNo, orderNos)
                .eq(Objects.nonNull(orderNos) ,BpmOARefundDO::getStatus, 0));
    }

    @Override
    public void updateStatus(String orderNo) {
        refundMapper.update(null, new LambdaUpdateWrapper<BpmOARefundDO>()
                .set(BpmOARefundDO::getStatus, 1)
                .eq(BpmOARefundDO::getOrderNo, orderNo)
                .eq(BpmOARefundDO::getStatus, 0));
    }
}
