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

import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
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.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.expenses.*;
import cn.iocoder.yudao.module.bpm.convert.oa.BpmOAExpensesConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.financialpayment.FinancialPaymentDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAExpensesDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAExpensesItemDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAExpensesItemMapper;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAExpensesMapper;
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.smartfactory.api.factoryInfo.FactoryInfoApi;
import cn.iocoder.yudao.module.smartfactory.api.factoryInfo.dto.FactoryInfoDTO;
import cn.iocoder.yudao.module.smartfactory.api.staff.StaffApi;
import cn.iocoder.yudao.module.smartfactory.api.staff.dto.StaffDTO;
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.loan.LoanApi;
import cn.iocoder.yudao.module.system.api.loan.dto.LoanDTO;
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 com.baomidou.mybatisplus.core.metadata.IPage;
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.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_EXPENSES_NOT_EXISTS;

/**
 * OA 生产开支 Service 实现类
 *
 * @author 符溶馨
 */
@Service
@Validated
public class BpmOAExpensesServiceImpl extends BpmOABaseService implements BpmOAExpensesService {

    /**
     * OA 生产开支对应的流程定义 KEY
     */
    public static final String PROCESS_KEY = "oa_expenses_2";

    @Resource
    private BpmOAExpensesMapper expensesMapper;

    @Resource
    private BpmOAExpensesItemMapper expensesItemMapper;

    @Resource
    private BpmProcessInstanceService processInstanceService;

    @Resource
    private FactoryInfoApi factoryInfoApi;

    @Resource
    private BankApi bankApi;

    @Resource
    private FinancialPaymentService financialPaymentService;

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

    @Resource
    private AdminUserApi userApi;

    @Resource
    private LoanApi loanApi;

    @Resource
    private StaffApi staffApi;

    @Override
    public Long createExpenses(Long userId, BpmOAExpensesCreateReqVO createReqVO) {

        //插入OA 生产开支申请
        BpmOAExpensesDO expenses = BpmOAExpensesConvert.INSTANCE.convert(createReqVO)
                .setUserId(userId)
                .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
        expensesMapper.insert(expenses);

        // 批量插入生产开支明细数据
        List<BpmOAExpensesItemDO> bpmOAExpensesItemDOS = BeanUtils.toBean(createReqVO.getExpensesItem(), BpmOAExpensesItemDO.class);
        bpmOAExpensesItemDOS.forEach(item -> item.setExpensesId(expenses.getId()));
        expensesItemMapper.insertBatch(bpmOAExpensesItemDOS);

        // 发起 BPM 流程
        Map<String, Object> processInstanceVariables = new HashMap<>();
        String type = bpmOAExpensesItemDOS.stream().map(item -> item.getType().toString()).collect(Collectors.joining(","));
        processInstanceVariables.put("type", type);
        processInstanceVariables.put("factoryType", expenses.getFactoryType());
        processInstanceVariables.put("factory_id", bpmOAExpensesItemDOS.get(0).getDeptId());
        String processInstanceId = processInstanceService.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY)
                        .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(expenses.getId())));

        // 将工作流的编号，更新到 OA 生产开支单中
        expensesMapper.updateById(new BpmOAExpensesDO().setId(expenses.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 expenses.getId();
    }

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

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

        //审核通过 （最后节点）
        if (BpmProcessInstanceResultEnum.APPROVE.getResult().equals(result)) {

            ProcessInstance instance = processInstanceService.getProcessInstance(processInstanceId);
            if (instance.isEnded()) {

                // 获取申请人对应的厂区员工编号
                StaffDTO staffDTO = staffApi.getStaffByUserId(expenses.getUserId()).getCheckedData();
                if (staffDTO != null) {

                    // 判断申请人是否存在 费用借支
                    LoanDTO loanDTO = loanApi.getByUserId(staffDTO.getId(), 2).getCheckedData();
                    if (loanDTO != null) {

                        if (loanDTO.getRemainingAmount().compareTo(expenses.getTotalMoney()) >= 0) {

                            // 设置开支日报支付状态为 已抵扣
                            expenses.setStatus(2).setAmountPaid(expenses.getTotalMoney());

                        }else {

                            // 设置开支日报支付状态为 未抵扣完毕
                            expenses.setStatus(3).setAmountPaid(loanDTO.getRemainingAmount());
                        }

                        // 更新借支表中 归还金额
                        loanApi.createLoan(new LoanDTO()
                                .setUserId(staffDTO.getId())
                                .setLoanType(2)
                                .setAmount(BigDecimal.ZERO)
                                .setReturnAmount(expenses.getAmountPaid()));
                    }
                }
            }
        }

        // 更新开支日报 审批结果、支付状态以及支付金额
        expensesMapper.updateById(new BpmOAExpensesDO()
                .setId(id)
                .setResult(result)
                .setStatus(expenses.getStatus())
                .setAmountPaid(expenses.getAmountPaid()));
    }

    private BpmOAExpensesDO validateLeaveExists(Long id) {
        BpmOAExpensesDO expenses = expensesMapper.selectById(id);
        if (expenses == null) {
            throw exception(OA_EXPENSES_NOT_EXISTS);
        }

        return expenses;
    }

    @Override
    public BpmOAExpensesDO getExpenses(Long id) {

        return expensesMapper.selectById(id);
    }

    @Override
    public List<BpmOAExpensesItemDO> getExpensesItem(Long expensesId) {

        return expensesItemMapper.selectList(BpmOAExpensesItemDO::getExpensesId, expensesId);
    }

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

    @Override
    public BpmOAExpensesRespVO convertExpenses(BpmOAExpensesDO expenses) {

        // 获取明细数据
        List<BpmOAExpensesItemDO> expensesItemDOs = getExpensesItem(expenses.getId());

        //获取部门信息map
        Map<Long, FactoryInfoDTO> factoryInfoDTOS = factoryInfoApi.getFactoryMap(convertSet(expensesItemDOs, BpmOAExpensesItemDO::getDeptId));

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

        return BpmOAExpensesConvert.INSTANCE.convert1(expenses, expensesItemDOs, factoryInfoDTOS, bankRespDTO);
    }

    @Override
    public PageResult<BpmOAExpensesPageRespVO> getExpensesPage(BpmOAExpensesPageReqVO pageReqVO) {
        IPage<BpmOAExpensesPageRespVO> page = expensesMapper.selectExpensesPage(MyBatisUtils.buildPage(pageReqVO) ,pageReqVO);
        return new PageResult<>(page.getRecords(), page.getTotal());
    }

    @Override
    public BpmOAExpensesTotal getExpensesTotal(BpmOAExpensesPageReqVO pageReqVO) {
        return expensesMapper.selectTotal(pageReqVO);
    }

    @Override
    public void getPayment(Long id) {

        // 获取开支详情
        BpmOAExpensesDO expenses = this.getExpenses(id);
        List<BpmOAExpensesItemDO> expensesItems = this.getExpensesItem(id);

        // 校验是否已添加至支付管理
        if (financialPaymentService.getFinancialPaymentByProcessInstanceId(expenses.getProcessInstanceId()) == null) {

            String reason = expensesItems.stream().map(BpmOAExpensesItemDO::getDetail)
                    .filter(StrUtil::isNotEmpty)
                    .collect(Collectors.joining(","));

            BpmProcessInstanceExtDO processInstance = processInstanceService.getProcessInstanceDO(expenses.getProcessInstanceId());
            // -- 插入到财务支付表中
            CommonResult<AdminUserRespDTO> user = userApi.getUser(expenses.getUserId());

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

            // 获取付款公司信息
            FactoryInfoDTO dto = new FactoryInfoDTO();
            if (expenses.getFactoryType() == 1) {
                dto = factoryInfoApi.getFactoryInfo(expensesItems.get(0).getDeptId()).getCheckedData();
            }

            financialPaymentService.save(new FinancialPaymentDO()
                    .setUserId(expenses.getUserId())
                    .setDeptId(user.getData() == null ? null : user.getData().getDeptId())
                    .setProcessInstanceId(expenses.getProcessInstanceId())
                    .setReason(reason)
                    .setObjectId(id)
                    .setType(9)
                    .setStatus(0)
                    .setAmountPayable(expenses.getTotalMoney())
                    .setProcessInstanceName(processInstance.getName())
                    .setBeginTime(processInstance.getCreateTime())
                    .setEndTime(processInstance.getEndTime())
                    .setRecipientName(bankRespDTO != null ? bankRespDTO.getNickname() + "," + bankRespDTO.getBankNo() + "," + bankRespDTO.getBankName() : "")
                    .setCompanyFactoryId(dto.getId())
            );
        }
    }

    @Override
    public void updateExpenses(Long id ,Integer status, BigDecimal amountPaid) {

        expensesMapper.updateById(new BpmOAExpensesDO()
                .setId(id)
                .setStatus(status)
                .setAmountPaid(amountPaid));
    }

    @Override
    public void refused(Long id, String processInstanceId) {

        // 设置状态为 驳回
        expensesMapper.updateById(new BpmOAExpensesDO()
                .setId(id)
                .setResult(BpmProcessInstanceResultEnum.BACK.getResult()));

        // 同步更新流程实例表
        processInstanceService.updateProcessInstanceResult(processInstanceId, BpmProcessInstanceResultEnum.BACK.getResult());
    }

    @Override
    public void oneClickPayment(String month) {

        expensesMapper.oneClickPayment(month);
    }
}
