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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.Constants;
import cn.iocoder.yudao.framework.common.enums.SocialTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.template.WxMpMsgTemplateUtils;
import cn.iocoder.yudao.framework.common.template.dto.ApprovalResultNotificationMessageDTO;
import cn.iocoder.yudao.framework.common.template.vo.SubscribeMessageReqDTO;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.financialpayment.vo.FinancialPaymentPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.financialpayment.vo.FinancialPaymentSaveReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.financialpayment.vo.FinancialPaymentSaveVO;
import cn.iocoder.yudao.module.bpm.controller.admin.financialpaymentitem.vo.FinancialPaymentItemSaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.financialpayment.FinancialPaymentDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.financialpaymentitem.FinancialPaymentItemDO;
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.financialpayment.FinancialPaymentMapper;
import cn.iocoder.yudao.module.bpm.dal.mysql.financialpaymentitem.FinancialPaymentItemMapper;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.*;
import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import cn.iocoder.yudao.module.system.api.auth.AdminOauthUserOtherInfoApi;
import cn.iocoder.yudao.module.system.api.auth.dto.AdminOauthUserOtherInfoApiDTO;
import cn.iocoder.yudao.module.system.api.auth.vo.AdminOauthUserOtherInfoApiVO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
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.subscribe.SubscribeMessageSendApi;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;

/**
 * 财务支付管理 Service 实现类
 *
 * @author 艾楷
 */
@Service
@Slf4j
@Validated
public class FinancialPaymentServiceImpl implements FinancialPaymentService {

    @Resource
    private FinancialPaymentMapper financialPaymentMapper;
    @Resource
    private FinancialPaymentItemMapper financialPaymentItemMapper;
    @Resource
    private BpmOACashMapper cashMapper;
    @Resource
    private BpmOAImprestMapper imprestMapper;
    @Resource
    private BpmOAProcurePayMapper oAProcurePayMapper;
    @Resource
    private BpmOAReimbursementMapper reimbursementMapper;
    @Resource
    private BpmOASupplierPurchasePaymentMapper supplierPurchasePaymentMapper;
    @Resource
    private SubscribeMessageSendApi subscribeMessageSendApi;
    @Resource
    private LoanApi loanApi;
    @Resource
    private BpmProcessInstanceExtMapper processInstanceExtMapper;
    @Resource
    private DeptApi deptApi;
    @Resource
    private AdminOauthUserOtherInfoApi adminOauthUserOtherInfoApi;

    @Resource
    private BpmOALoanMapper loanMapper;

    @Resource
    private BpmOAPaymentMapper paymentMapper;

    @Resource
    private BpmOASalaryMapper salaryMapper;

    @Resource
    private BpmOAExpensesMapper expensesMapper;

    @Override
    public Long createFinancialPayment(FinancialPaymentSaveVO vo) {
        FinancialPaymentItemSaveReqVO createReqVO = vo.getFinancialPaymentItemSaveReqVO();
        // 插入子表
        createReqVO.setFinancialPaymentId(vo.getId());
        FinancialPaymentItemDO financialPaymentItem = BeanUtils.toBean(createReqVO, FinancialPaymentItemDO.class);
        financialPaymentItemMapper.insert(financialPaymentItem);

        // 查询财务支付信息 - 并且更新
        FinancialPaymentDO financialPayment = this.getFinancialPayment(vo.getId());
        if (vo.getStatus() == 1) {
            financialPayment.setActualPayment(financialPayment.getActualPayment().add(financialPaymentItem.getAmount()));
            if (vo.getType() != null && vo.getType() == 1) {
                financialPayment.setStatus(2);
            } else {
                if (financialPayment.getActualPayment().compareTo(financialPayment.getAmountPayable()) >= 0) {
                    financialPayment.setStatus(2);
                } else {
                    financialPayment.setStatus(1);
                }
            }
        } else {
            // TODO: 2024/8/18  - 驳回该流程通过流程实例id 发消息通知发起人 - 更改状态
            financialPayment.setStatus(3);
            // -- 修改流程状态
            switch (financialPayment.getType()) {
                case 1:
                    cashMapper.updateById(new BpmOACashDO().setId(financialPayment.getObjectId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult()));
                    break;
                case 2:
                    imprestMapper.updateById(new BpmOAImprestDO().setId(financialPayment.getObjectId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult()));
                    break;
                case 3:
                    oAProcurePayMapper.updateById(new BpmOAProcurePayDO().setId(financialPayment.getObjectId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult()));
                    break;
                case 4:
                    reimbursementMapper.updateById(new BpmOAReimbursementDO().setId(financialPayment.getObjectId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult()));
                    break;
                case 5:
                    paymentMapper.updateById(new BpmOAPaymentDO().setId(financialPayment.getObjectId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult()));
                    break;
                case 6:
                    salaryMapper.updateById(new BpmOASalaryDO().setId(financialPayment.getObjectId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult()));
                   break;
                case 7:
                    loanMapper.updateById(new BpmOALoanDO().setId(financialPayment.getObjectId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult()));
                    break;
                case 8:
                    supplierPurchasePaymentMapper.updateById(new BpmOASupplierPurchasePaymentDO().setId(financialPayment.getObjectId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult()));
                    break;
                case 9:
                    expensesMapper.updateById(new BpmOAExpensesDO().setId(financialPayment.getObjectId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult()));
            }
            BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(financialPayment.getProcessInstanceId())
                    .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
                    .setResult(BpmProcessInstanceResultEnum.BACK.getResult());
            processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
        }
        this.updateById(financialPayment);

        // 支付完成时
        if (financialPayment.getStatus() == 2) {

            switch (financialPayment.getType()) {
                case 7: // 借支申请时
                    // 获取借支信息
                    BpmOALoanDO loanDO = loanMapper.selectById(financialPayment.getObjectId());

                    // 同步插入借支表中
                    LoanDTO createDO = new LoanDTO()
                            .setUserId(loanDO.getSfUserId())
                            .setDeptId(loanDO.getFactoryId())
                            .setLoanType(loanDO.getLoanType())
                            .setAmount(loanDO.getTotalMoney())
                            .setReturnAmount(BigDecimal.ZERO);
                    loanApi.createLoan(createDO);
                    break;
                case 9:  // 开支日报时
                    // 更新开支日报支付状态、支付金额
                    expensesMapper.updateById(new BpmOAExpensesDO()
                            .setId(financialPayment.getObjectId())
                            .setStatus(1)
                            .setAmountPaid(financialPayment.getActualPayment()));

            }
        }

        // -- 获取发起人的openId
        AdminOauthUserOtherInfoApiVO item = adminOauthUserOtherInfoApi.getByCondition(
                new AdminOauthUserOtherInfoApiDTO().setUserIds(Collections.singletonList(financialPayment.getUserId()))
                        .setSocialType(SocialTypeEnum.WECHAT_MP.getType())).getCheckedData();
        if (item != null && StrUtil.isNotEmpty(item.getOpenId())) {
            try {
                String result = vo.getStatus() == 1 ? "通过" : "拒绝";
                String page = vo.getStatus() == 1 ? "subPages/paymentVoucher/paymentVoucher?id=" + financialPayment.getId() :
                        "subPagesB/bpm/task/todo/examineApprove?id=" + financialPayment.getId() + "&isDetail=1";
                // --- 发消息通知发起人
                String time = financialPaymentItem.getCreateTime().format(Constants.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND);
                SubscribeMessageReqDTO dto = new WxMpMsgTemplateUtils().convertApprovalResultNotification(new ApprovalResultNotificationMessageDTO()
                        .setOpenId(item.getOpenId())
                        .setProcessInstanceName(financialPayment.getProcessInstanceName())
                        .setTime(time)
                        .setResult(result)
                        .setReason((StrUtil.isEmpty(financialPaymentItem.getNotes()) ? result : financialPaymentItem.getNotes()))
                        .setMiniProgramState("formal")
                        .setPage(page));
                subscribeMessageSendApi.sendMpMsg(dto);
            } catch (Exception e) {
                log.error("财务管理审批发送通知人失败:{}", financialPayment);
            }
        }
        // 返回
        return financialPayment.getId();
    }

    @Override
    public void updateFinancialPayment(FinancialPaymentSaveReqVO updateReqVO) {
        // 更新
        FinancialPaymentDO updateObj = BeanUtils.toBean(updateReqVO, FinancialPaymentDO.class);
        financialPaymentMapper.updateById(updateObj);
    }

    @Override
    public void deleteFinancialPayment(Long id) {
        // 删除
        financialPaymentMapper.deleteById(id);
    }

    @Override
    public FinancialPaymentDO getFinancialPayment(Long id) {
        return financialPaymentMapper.getFinancialPayment(id);
    }

    @Override
    public PageResult<FinancialPaymentDO> getFinancialPaymentPage(FinancialPaymentPageReqVO pageReqVO) {
        pageReqVO.setReceiveUserId(getLoginUserId());
        List<Long> deptIds = new ArrayList<>();
        if (pageReqVO.getDeptId() != null) {
            deptIds.add(pageReqVO.getDeptId());
            CommonResult<List<Long>> childDeptList = deptApi.getChildDeptList(pageReqVO.getDeptId());
            if (childDeptList.isSuccess()) {
                deptIds.addAll(childDeptList.getData());
            }
            pageReqVO.setDeptIds(deptIds);
        }
        IPage<FinancialPaymentDO> vos = financialPaymentMapper.getFinancialPaymentPage(pageReqVO, MyBatisUtils.buildPage(pageReqVO));
        return new PageResult<>(vos.getRecords(), vos.getTotal());
    }

    @Override
    public void save(FinancialPaymentDO financialPaymentDO) {
        financialPaymentMapper.insert(financialPaymentDO);
    }

    @Override
    public void receive(List<Long> ids) {
        Long userId = getLoginUserId();
        financialPaymentMapper.update(new FinancialPaymentDO().setReceiveUserId(userId), new LambdaQueryWrapper<FinancialPaymentDO>()
                .in(FinancialPaymentDO::getId, ids));
    }

    @Override
    public void updateById(FinancialPaymentDO financialPayment) {
        financialPaymentMapper.updateById(financialPayment);
    }

    @Override
    public void cancel(List<Long> ids) {
        financialPaymentMapper.cancel(ids);
    }

    @Override
    public List<FinancialPaymentDO> getFinancialPaymentList(List<String> processInstanceIds) {

        return financialPaymentMapper.selectList(FinancialPaymentDO::getProcessInstanceId, processInstanceIds);

    }

    @Override
    public FinancialPaymentDO getFinancialPaymentByProcessInstanceId(String processInstanceId) {
        List<FinancialPaymentDO> financialPaymentDO = financialPaymentMapper.selectList(FinancialPaymentDO::getProcessInstanceId, processInstanceId);
        if (CollUtil.isNotEmpty(financialPaymentDO)) {
            return financialPaymentDO.get(0);
        }
        return null;
    }

    @Override
    public FinancialPaymentDO getPaymentTotal(FinancialPaymentPageReqVO pageReqVO) {
        pageReqVO.setReceiveUserId(getLoginUserId());
        return financialPaymentMapper.selectPaymentTotal(pageReqVO);
    }

    @Override
    public void exportList(HttpServletResponse response, FinancialPaymentPageReqVO pageReqVO) {

        try {
            if (pageReqVO.getStatus() == null) {
                pageReqVO.setStatus(0);
            }

            // 获取支付信息数据
            List<FinancialPaymentDO> list = this.getFinancialPaymentPage(pageReqVO).getList();
            list.removeIf(item -> item.getCompanyId() == null);

            // 根据付款公司进行分组
            Map<Long, List<FinancialPaymentDO>> companyIdMap = list.stream().collect(Collectors.groupingBy(FinancialPaymentDO::getCompanyId));

            List<List<String>> data = new ArrayList<>();
            for (Map.Entry<Long, List<FinancialPaymentDO>> entry : companyIdMap.entrySet()) {

                // 设置表头
                data.add(Arrays.asList(entry.getValue().get(0).getCompanyName(), entry.getValue().get(0).getCompanyName(), entry.getValue().get(0).getCompanyName(),
                        entry.getValue().get(0).getCompanyName(), entry.getValue().get(0).getCompanyName(), entry.getValue().get(0).getCompanyName(), entry.getValue().get(0).getCompanyName()));
                data.add(this.setTitle());

                // 提取所有公账 支付信息
                List<FinancialPaymentDO> publicAccount = entry.getValue().stream()
                        .filter(item -> item.getType() != 9)
                        .collect(Collectors.toList());
                // 提取所有私账 支付信息
                List<FinancialPaymentDO> privateAccount = entry.getValue().stream()
                        .filter(item -> item.getType() == 9)
                        .collect(Collectors.toList());

                // 遍历公账信息
                for (FinancialPaymentDO financialPaymentDO : publicAccount) {
                    List<String> row = new ArrayList<>();
                    // 设置姓名
                    row.add(financialPaymentDO.getNickname());
                    // 设置报销部门
                    row.add(financialPaymentDO.getDeptName());
                    // 添加事项
                    row.add(financialPaymentDO.getReason());
                    // 添加金额
                    row.add(financialPaymentDO.getAmountPayable().toString());
                    // 添加卡号
                    row.add(financialPaymentDO.getRecipientName());
                    // 添加公账/私账
                    row.add("公账");
                    // 添加备注
                    row.add(financialPaymentDO.getNickname() + "报销");

                    data.add(row);
                }

                if (CollUtil.isNotEmpty(publicAccount)) {
                    // 添加公账合计数据
                    BigDecimal publicAccountTotal = publicAccount.stream().map(FinancialPaymentDO::getAmountPayable).reduce(BigDecimal.ZERO, BigDecimal::add);
                    data.add(Arrays.asList("公账总计", "公账总计", "公账总计", publicAccountTotal.toString(), "", "公账"));
                }

                // 遍历私账信息
                for (FinancialPaymentDO financialPaymentDO : privateAccount) {
                    List<String> row = new ArrayList<>();
                    // 设置姓名
                    row.add(financialPaymentDO.getNickname());
                    // 设置报销部门
                    row.add(financialPaymentDO.getDeptName());
                    // 添加事项
                    row.add(financialPaymentDO.getReason());
                    // 添加金额
                    row.add(financialPaymentDO.getAmountPayable().toString());
                    // 添加卡号
                    row.add(financialPaymentDO.getRecipientName());
                    // 添加公账/私账
                    row.add("私账");
                    // 添加备注
                    row.add(financialPaymentDO.getNickname() + "报销");

                    data.add(row);
                }

                if (CollUtil.isNotEmpty(privateAccount)) {
                    // 添加私账合计数据
                    BigDecimal privateAccountTotal = privateAccount.stream().map(FinancialPaymentDO::getAmountPayable).reduce(BigDecimal.ZERO, BigDecimal::add);
                    data.add(Arrays.asList("私账总计", "私账总计", "私账总计", privateAccountTotal.toString(), "", "私账"));
                }
            }

            EasyExcel.write(response.getOutputStream())
                    .autoCloseStream(false)
                    .excelType(ExcelTypeEnum.XLSX)
                    .sheet("账目明细")
                    .registerWriteHandler(new CustomCellStyleHandler())
                    .registerWriteHandler(new MergeSameRowCellsHandler())
                    .doWrite(data);

            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("账目", StandardCharsets.UTF_8.name()));
            response.setContentType("application/vnd.ms-excel;charset=UTF-8");

        } catch (IOException e) {
            log.error("[exportList][导出失败]", e);
            throw new RuntimeException(e);
        }
    }

    private List<String> setTitle() {
        List<String> titleRow = new ArrayList<>();
        titleRow.add("姓名");
        titleRow.add("报销部门");
        titleRow.add("事项");
        titleRow.add("金额");
        titleRow.add("卡号");
        titleRow.add("公账/私账");
        titleRow.add("备注");

       return titleRow;
    }
}
