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

import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.UploadUserFile;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
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.invoice.BpmOAInvoiceCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.invoice.BpmOAInvoicePageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.invoice.BpmOAInvoiceRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAContractDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAInvoiceDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAInvoiceMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmHistoryProcessInstanceService;
import cn.iocoder.yudao.module.hrm.api.userlivetree.UserLiveTreeApi;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
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.concurrent.TimeUnit;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.SECOND_MILLIS;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;

/**
 * OA 开票申请 Service 实现类
 *
 * @author 符溶馨
 */
@Service
@Validated
public class BpmOAInvoiceServiceImpl extends BpmOABaseService implements BpmOAInvoiceService {

    /**
     * OA 开票申请对应的流程定义 KEY
     */
    public static final String PROCESS_KEY = "oa_invoice_2";

    @Resource
    private BpmOAInvoiceMapper invoiceMapper;

    @Resource
    private BpmProcessInstanceApi processInstanceApi;

    @Resource
    private BpmHistoryProcessInstanceService historyProcessInstanceService;

    @Resource
    private BpmOAContractService contractService;

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private UserLiveTreeApi userLiveTreeApi;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createInvoice(Long userId, BpmOAInvoiceCreateReqVO createReqVO) {

        // 校验关联合同 是否还有开票余额
        validateContractBalance(createReqVO.getContractId(), createReqVO.getAmount());

        //插入OA 开票申请
        BpmOAInvoiceDO invoice = BeanUtils.toBean(createReqVO, BpmOAInvoiceDO.class)
                .setUserId(userId)
                .setStatus(0)
                .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
        invoiceMapper.insert(invoice);

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

        // 将工作流的编号，更新到 OA 开票申请单中
        invoiceMapper.updateById(new BpmOAInvoiceDO().setId(invoice.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);
        }

        return invoice.getId();
    }

    private void validateContractBalance(Long contractId, BigDecimal amount) {

        String lockKey = "bpm:invoice:create_" + contractId;
        // -- 分布式锁
        RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock(120 * SECOND_MILLIS, TimeUnit.MILLISECONDS);

            // 获取合同信息
            BpmOAContractDO contractDO = contractService.getContract(contractId);
            if (contractDO == null) {
                throw exception(OA_CONTRACT_NOT_EXISTS);
            }

            // 判断是否还有开票余额
            BigDecimal balance = contractDO.getReturnMoney().subtract(contractDO.getInvoiceMoney());
            if (balance.compareTo(amount) <= 0) {
                throw exception(OA_CONTRACT_INVOICE_AMOUNT_LACK);
            }

            // 余额足够时 更新合同的开票金额
            contractService.updateContract(new BpmOAContractDO()
                    .setId(contractDO.getId())
                    .setInvoiceMoney(contractDO.getInvoiceMoney().add(amount)));

        } finally {
            lock.unlock();
        }
    }

    @Override
    public void updateInvoiceResult(Long id, Integer result) {

        BpmOAInvoiceDO invoiceDO = validateLeaveExists(id);
        invoiceMapper.updateById(new BpmOAInvoiceDO().setId(id).setResult(result));

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

            String lockKey = "bpm:invoice:create_" + invoiceDO.getContractId();
            // -- 分布式锁
            RLock lock = redissonClient.getLock(lockKey);
            try {
                lock.lock(120 * SECOND_MILLIS, TimeUnit.MILLISECONDS);

                // 取消时，回复合同之前的开票金额
                contractService.updateInvoiceMoney(invoiceDO.getContractId(), invoiceDO.getAmount());

            } finally {
                lock.unlock();
            }

        }
    }

    private BpmOAInvoiceDO validateLeaveExists(Long id) {
        BpmOAInvoiceDO invoice = invoiceMapper.selectById(id);
        if (invoice == null) {
            throw exception(OA_INVOICE_NOT_EXISTS);
        }

        return invoice;
    }

    @Override
    public BpmOAInvoiceDO getInvoice(Long id) {

        return invoiceMapper.selectById(id);
    }

    @Override
    public BpmOAInvoiceDO getByProcessInstanceId(String processInstanceId) {

        List<BpmOAInvoiceDO> bpmOAInvoiceDOS = invoiceMapper.selectList(BpmOAInvoiceDO::getProcessInstanceId, processInstanceId);
        if (CollectionUtil.isEmpty(bpmOAInvoiceDOS)) {
            return null;
        }
        return bpmOAInvoiceDOS.get(0);
    }

    @Override
    public PageResult<BpmOAInvoiceRespVO> getInvoicePage(BpmOAInvoicePageReqVO pageReqVO) {
        Long loginUserId = getLoginUserId();
        List<Long> userIds = new ArrayList<>();
        if ("my".equals(pageReqVO.getRelation())) {
            userIds.add(loginUserId);
        } else {
            userIds = userLiveTreeApi.getItemIdsByUserId(loginUserId).getCheckedData();
        }
        return invoiceMapper.selectPage(pageReqVO, userIds);
    }

    @Override
    public List<BpmOAInvoiceRespVO> getListByProcessInstanceIds(List<String> processInstanceIds) {

        return invoiceMapper.selectListByProcessInstanceIds(processInstanceIds);
    }
}
