
package com.tmerclub.cloud.payment.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.tmerclub.cloud.api.order.constant.FinanceType;
import com.tmerclub.cloud.api.order.feign.OrderRefundFeignClient;
import com.tmerclub.cloud.api.order.vo.AccountOrderDetailVO;
import com.tmerclub.cloud.api.order.vo.PageShopAccountOrderVO;
import com.tmerclub.cloud.api.order.vo.ShopAccountOrderDetailVO;
import com.tmerclub.cloud.api.payment.vo.AccountDetailVO;
import com.tmerclub.cloud.api.payment.vo.ShopAccountDetailVO;
import com.tmerclub.cloud.api.platform.feign.ConfigFeignClient;
import com.tmerclub.cloud.api.user.feign.UserLevelAndScoreOrderFeignClient;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.constant.PayType;
import com.tmerclub.cloud.common.database.dto.PageDTO;
import com.tmerclub.cloud.common.database.util.PageUtil;
import com.tmerclub.cloud.common.database.vo.PageVO;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.order.bo.PayNotifyBO;
import com.tmerclub.cloud.common.order.bo.PayRefundBO;
import com.tmerclub.cloud.common.order.bo.RefundNotifyBO;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.common.response.ServerResponseEntity;
import com.tmerclub.cloud.common.rocketmq.config.RocketMqConstant;
import com.tmerclub.cloud.common.util.Arith;
import com.tmerclub.cloud.common.util.BeanUtil;
import com.tmerclub.cloud.common.util.ExcelUtil;
import com.tmerclub.cloud.common.util.PriceUtil;
import com.tmerclub.cloud.payment.bo.RefundInfoBO;
import com.tmerclub.cloud.payment.constant.PayEntry;
import com.tmerclub.cloud.payment.constant.PayStatus;
import com.tmerclub.cloud.payment.constant.RefundStatus;
import com.tmerclub.cloud.payment.dto.AccountSearchDTO;
import com.tmerclub.cloud.payment.manager.AllinpayManager;
import com.tmerclub.cloud.payment.manager.PayManager;
import com.tmerclub.cloud.payment.mapper.PayInfoMapper;
import com.tmerclub.cloud.payment.mapper.RefundInfoMapper;
import com.tmerclub.cloud.payment.model.PayInfo;
import com.tmerclub.cloud.payment.model.RefundInfo;
import com.tmerclub.cloud.payment.service.RefundInfoService;
import com.tmerclub.cloud.payment.vo.RefundInfoExcelVO;
import com.tmerclub.cloud.payment.vo.RefundInfoVO;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 退款信息
 *
 * @author tmerclub
 * @date 2021-03-11 14:45:01
 */
@Service
@RefreshScope
public class RefundInfoServiceImpl implements RefundInfoService {

    private static final Logger log = LoggerFactory.getLogger(RefundInfoServiceImpl.class);

    @Autowired
    private RefundInfoMapper refundInfoMapper;
    @Autowired
    private PayInfoMapper payInfoMapper;
    @Autowired
    private PayManager payManager;
    @Autowired
    private AllinpayManager allinpayManager;
    @Value("${application.domainUrl}")
    private String domainUrl;
    @Autowired
    private RocketMQTemplate orderRefundSuccessTemplate;
    @Autowired
    private RocketMQTemplate allinPayBalanceOrderSuccessTemplate;
    @DubboReference
    private UserLevelAndScoreOrderFeignClient userLevelAndScoreOrderFeignClient;
    @DubboReference
    private OrderRefundFeignClient orderRefundFeignClient;
    @DubboReference
    private ConfigFeignClient configFeignClient;

    @Override
    public PageVO<RefundInfo> page(PageDTO pageDTO) {
        return PageUtil.doPage(pageDTO, () -> refundInfoMapper.list());
    }

    @Override
    public RefundInfo getByRefundIdAndWxPay(Long refundId) {
        return refundInfoMapper.listByRefundIdAndWxPay(refundId).get(0);
    }

    @Override
    public RefundInfo getByRefundId(Long refundId) {
        List<RefundInfo> refundInfos = refundInfoMapper.listByRefundId(refundId);
        if (CollUtil.isEmpty(refundInfos)) {
            return null;
        }
        return refundInfos.get(0);
    }

    @Override
    public void save(RefundInfo refundInfo) {
        refundInfoMapper.save(refundInfo);
    }

    @Override
    public void update(RefundInfo refundInfo) {
        refundInfoMapper.update(refundInfo);
    }

    @Override
    public void deleteById(Long refundId) {
        refundInfoMapper.deleteById(refundId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void doRefund(PayRefundBO payRefundBO) {
        if (refundInfoMapper.countByRefundId(payRefundBO.getRefundId()) > 0) {
            return;
        }
        List<PayInfo> payInfoList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        // 预售订单可能有两笔
        if (StrUtil.isNotBlank(payRefundBO.getPayIds())) {
            payInfoList = getPayInfoList(payRefundBO);
        } else {
            PayInfo payInfo = payInfoMapper.getByPayId(payRefundBO.getPayId());
            payInfoList.add(payInfo);
        }
        if (CollectionUtil.isEmpty(payInfoList)) {
            return;
        }
        boolean refundSuccess = true;
        RefundInfo successRefundInfo = new RefundInfo();
        Long refundAmount = payRefundBO.getRefundAmount();
        int payNumber = 1;
        payInfoList.sort(Comparator.comparing(PayInfo::getPayAmount).reversed());
        // 获取订单之前是不是有进行过采购，如果有进行过采购，那么本次支付将直接退款
        boolean refundPurchaseAmount = Objects.nonNull(payRefundBO.getAllinpayRefundPurchaseAmount()) && payRefundBO.getAllinpayRefundPurchaseAmount() > 0;
        if (refundPurchaseAmount) {
            PayInfo payInfo = payInfoMapper.getByOrderIdsAndEntry(payRefundBO.getOrderId().toString(), PayEntry.SHOP_BALANCE_PAY_PURCHASE.value()).get(0);
            payInfoList.add(payInfo);
        }
        for (int i = 0; i < payInfoList.size(); i++) {
            PayInfo payInfo = payInfoList.get(i);
            RefundInfo refundInfo = getRefundInfo(payRefundBO, payInfoList, refundAmount, refundPurchaseAmount, i, payInfo);
            // 保存退款信息 refund_id是唯一（定金预售时不唯一）的，所以能确保只进行一次退款
            refundInfoMapper.save(refundInfo);

            RefundInfoBO refundInfoBO = new RefundInfoBO();
            refundInfoBO.setRefundId(refundInfo.getRefundId());
            refundInfoBO.setRefundAmount(refundInfo.getRefundAmount());
            refundInfoBO.setPayType(PayType.instance(payInfo.getPayType()));
            refundInfoBO.setPayId(payInfo.getPayId());
            refundInfoBO.setBizUserId(payRefundBO.getShopId());
            refundInfoBO.setPayAmount(payInfo.getPayAmount());
            refundInfoBO.setNotifyUrl(domainUrl + "/ua/notice/refund/order/" + payInfo.getPaySysType() + "/" + payInfo.getPayType());

            refundInfoBO.setUserId(payInfo.getUserId());
            refundInfoBO.setRefundNumber(payNumber++);
            // 余额支付，直接退款都不需要回调的,只要有一个是需要回调则等回调
            if (Objects.equals(payInfo.getPaySysType(), 0)) {
                log.info("开始进行普通退款{}",refundInfoBO);
                refundSuccess = payManager.doRefund(refundInfoBO);
            } else {
                log.info("开始进行通联退款{}",refundInfoBO);
                refundSuccess = allinpayManager.doRefund(refundInfoBO);
            }

            if (!Objects.equals(payRefundBO.getOnlyRefund(), 1) && refundSuccess) {
                refundInfo.setCallbackTime(new Date());
                refundInfo.setRefundStatus(RefundStatus.REFUNDED.value());
                refundInfoMapper.updateByIdAndPayId(refundInfo);
                successRefundInfo = refundInfo;
            }
            refundAmount = refundAmount - payInfo.getPayAmount();
            if (refundAmount <= 0) {
                break;
            }
        }
        // 是否直接进行退款，不需要任何回调
        if (!Objects.equals(payRefundBO.getOnlyRefund(), 1) && refundSuccess) {
            successRefundInfo.setRefundStatus(RefundStatus.UNREFUND.value());
            // 如果都不需要回调，则直接使用第一个
            refundSuccess(successRefundInfo);
        }
    }

    @NotNull
    private static RefundInfo getRefundInfo(PayRefundBO payRefundBO, List<PayInfo> payInfoList, Long refundAmount, boolean refundPurchaseAmount, int i, PayInfo payInfo) {
        RefundInfo refundInfo = new RefundInfo();
        refundInfo.setRefundId(payRefundBO.getRefundId());
        refundInfo.setRefundAmount(Math.min(refundAmount, payInfo.getPayAmount()));
        // 如果是最后一项，且采购需退款金额不为空，则进行商家采购金额退款
        if (refundPurchaseAmount && i >= payInfoList.size() - 1) {
            refundInfo.setRefundAmount(Math.min(payRefundBO.getAllinpayRefundPurchaseAmount(), payInfo.getPayAmount()));
        }
        refundInfo.setOrderId(payRefundBO.getOrderId());
        refundInfo.setPayId(payInfo.getPayId());
        refundInfo.setPayType(payInfo.getPayType());
        refundInfo.setUnSuccessGroupOrder(payRefundBO.getUnSuccessGroupOrder());
        refundInfo.setUserId(payInfo.getUserId());
        refundInfo.setRefundStatus(RefundStatus.UNREFUND.value());
        return refundInfo;
    }

    private List<PayInfo> getPayInfoList(PayRefundBO payRefundBO) {
        List<PayInfo> payInfoList;
        String[] payIds = payRefundBO.getPayIds().split(StrUtil.COMMA);
        List<Long> payIdList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        for (String payId : payIds) {
            try {
                payIdList.add(Long.valueOf(payId));
            } catch (Exception e) {
                log.error("支付idLong类型转换失败", e);
            }
        }
        payInfoList = payInfoMapper.listByPayIds(payIdList);
        return payInfoList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void refundSuccess(RefundInfo refundInfo) {
        // 已经退款
        if (Objects.equals(refundInfo.getRefundStatus(), RefundStatus.REFUNDED.value())) {
            return;
        }
        refundInfo.setCallbackTime(new Date());
        refundInfo.setRefundStatus(RefundStatus.REFUNDED.value());
        refundInfoMapper.update(refundInfo);
        // TODO 如果是多条退款，需要判断最后一条才进行回调?
        RefundNotifyBO refundNotifyBO = new RefundNotifyBO();
        refundNotifyBO.setRefundId(refundInfo.getRefundId());
        refundNotifyBO.setRefundAmount(refundInfo.getRefundAmount());
        refundNotifyBO.setOrderId(refundInfo.getOrderId());
        refundNotifyBO.setPayId(refundInfo.getPayId());
        refundNotifyBO.setUnSuccessGroupOrder(refundInfo.getUnSuccessGroupOrder());
        // 通知订单将订单的退款状态改变
        SendStatus sendOrderStatus = orderRefundSuccessTemplate.syncSend(RocketMqConstant.ORDER_REFUND_SUCCESS_TOPIC, new GenericMessage<>(refundNotifyBO)).getSendStatus();


        if (!Objects.equals(sendOrderStatus, SendStatus.SEND_OK)) {
            // 这个回调方法会多次进行回调
            throw new LuckException(ResponseEnum.EXCEPTION);
        }

    }

    @Override
    public AccountDetailVO getRefundAccountDetail(Date startTime, Date endTime, String shopName) {
        ServerResponseEntity<AccountOrderDetailVO> refundResponse = orderRefundFeignClient.getAccountOrderDetail(startTime, endTime, shopName, null);
        if (!refundResponse.isSuccess()) {
            throw new LuckException(refundResponse.getMsg());
        }
        AccountDetailVO accountDetailVO = BeanUtil.map(refundResponse.getData(), AccountDetailVO.class);
        if (Objects.isNull(accountDetailVO)) {
            AccountDetailVO accountDetail = new AccountDetailVO();
            accountDetail.setWechatAmount(0L);
            accountDetail.setAlipayAmount(0L);
            accountDetail.setBalanceAmount(0L);
            accountDetail.setScoreCount(0L);
            accountDetail.setWechatPercent(0.00);
            accountDetail.setAlipayPercent(0.00);
            accountDetail.setBalancePercent(0.00);
            accountDetail.setTotal(0L);
            return accountDetail;
        } else {
            getTotal(accountDetailVO);
            return accountDetailVO;
        }
    }

    @Override
    public AccountDetailVO getPlatformRefundAccountDetail(Date startTime, Date endTime) {
        ServerResponseEntity<AccountOrderDetailVO> platformOrderResponse = orderRefundFeignClient.getAccountOrderDetail(startTime, endTime, "", Constant.PLATFORM_SHOP_ID);
        if (!platformOrderResponse.isSuccess()) {
            throw new LuckException(platformOrderResponse.getMsg());
        }
        AccountOrderDetailVO accountOrderDetailVO = platformOrderResponse.getData();
        AccountDetailVO accountDetailVO = new AccountDetailVO();
        accountDetailVO.setAlipayAmount(Objects.isNull(accountOrderDetailVO.getAlipayAmount()) ? 0L : accountOrderDetailVO.getAlipayAmount());
        accountDetailVO.setWechatAmount(Objects.isNull(accountOrderDetailVO.getWechatAmount()) ? 0L : accountOrderDetailVO.getWechatAmount());
        accountDetailVO.setBalanceAmount(Objects.isNull(accountOrderDetailVO.getBalanceAmount()) ? 0L : accountOrderDetailVO.getBalanceAmount());
        accountDetailVO.setScoreCount(Objects.isNull(accountOrderDetailVO.getScoreCount()) ? 0L : accountOrderDetailVO.getScoreCount());
        accountDetailVO.setTotal(accountDetailVO.getAlipayAmount() + accountDetailVO.getBalanceAmount() + accountDetailVO.getWechatAmount());
        return accountDetailVO;
    }

    @Override
    public PageVO<RefundInfoVO> getRefundInfoPage(PageDTO pageDTO, AccountSearchDTO accountSearchDTO) {
        PageVO<RefundInfoVO> pageVO = new PageVO<>();
        ServerResponseEntity<PageShopAccountOrderVO> orderResponse = orderRefundFeignClient.listOrderRefundInfo(accountSearchDTO.getStartTime(), accountSearchDTO.getEndTime(), accountSearchDTO.getShopId(), null, pageDTO.getPageSize(), pageDTO.getPageNum());
        if (!orderResponse.isSuccess()) {
            throw new LuckException(orderResponse.getMsg());
        }
        pageVO.setTotal(orderResponse.getData().getTotal());
        pageVO.setList(BeanUtil.mapAsList(orderResponse.getData().getRefundInfoList(), RefundInfoVO.class));
        pageVO.setPages(PageUtil.getPages(orderResponse.getData().getTotal(), pageDTO.getPageSize()));
        return pageVO;
    }

    @Override
    public void excelRefundInfo(HttpServletResponse response, AccountSearchDTO accountSearchDTO) {
        accountSearchDTO.setFinanceType(FinanceType.ORDER.value());
        ServerResponseEntity<PageShopAccountOrderVO> orderRefundResponse = orderRefundFeignClient.listOrderRefundInfo(accountSearchDTO.getStartTime(), accountSearchDTO.getEndTime(), null, accountSearchDTO.getShopName(), null, null);
        if (!orderRefundResponse.isSuccess()) {
            throw new LuckException(orderRefundResponse.getMsg());
        }
        List<RefundInfoVO> orderRefundInfoList = BeanUtil.mapAsList(orderRefundResponse.getData().getRefundInfoList(), RefundInfoVO.class);

        Map<Integer, String[]> map = new HashMap<>(8);
        ExcelWriter excelWriter = null;
        try {
            // 先执行合并策略
            ExcelWriterBuilder excelWriterMerge = ExcelUtil.getExcelWriterMerge(response, RefundInfoExcelVO.EXCEL_NAME, RefundInfoExcelVO.MERGE_ROW_INDEX, RefundInfoExcelVO.MERGE_COLUMN_INDEX);
            excelWriter = ExcelUtil.getExcelModel(excelWriterMerge, map, Constant.START_ROW).build();
            // 业务代码
            writeExcel(orderRefundInfoList, FinanceType.ORDER.str(), excelWriter);
        } catch (Exception e) {
            log.error("导出财务对账退款报表出错", e);
        } finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    private List<RefundInfoExcelVO> getExcelList(List<RefundInfoVO> refundInfoList) {
        List<RefundInfoExcelVO> refundInfoExcelList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        int index = 1;
        for (RefundInfoVO refundInfoVO : refundInfoList) {
            RefundInfoExcelVO refundInfoExcelVO = new RefundInfoExcelVO();
            refundInfoExcelVO.setSeq(String.valueOf(index));
            refundInfoExcelVO.setShopName(refundInfoVO.getShopName());
            refundInfoExcelVO.setRefundId(refundInfoVO.getRefundId().toString());
            refundInfoExcelVO.setPayId(refundInfoVO.getPayId().toString());
            refundInfoExcelVO.setOrderId(refundInfoVO.getOrderId().toString());
            refundInfoExcelVO.setPayEntry(PayEntry.getPayTypeName(refundInfoVO.getPayEntry()));
            refundInfoExcelVO.setPayType(PayType.getPayTypeName(refundInfoVO.getPayType()));
            refundInfoExcelVO.setPayScore(Objects.isNull(refundInfoVO.getScoreCount()) ? "0" : refundInfoVO.getScoreCount().toString());
            refundInfoExcelVO.setAlipayAmount(PriceUtil.toDecimalPrice(refundInfoVO.getAlipayAmount()).toString());
            refundInfoExcelVO.setWechatAmount(PriceUtil.toDecimalPrice(refundInfoVO.getWechatAmount()).toString());
            refundInfoExcelVO.setBalanceAmount(PriceUtil.toDecimalPrice(refundInfoVO.getBalanceAmount()).toString());
            refundInfoExcelVO.setRefundTime(refundInfoVO.getRefundTime());
            refundInfoExcelList.add(refundInfoExcelVO);
            index++;
        }
        return refundInfoExcelList;
    }

    private void writeExcel(List<RefundInfoVO> refundInfoList, String type, ExcelWriter excelWriter) {
        List<RefundInfoExcelVO> refundInfoExcelList = getExcelList(refundInfoList);
        if (CollUtil.isNotEmpty(refundInfoExcelList)) {
            refundInfoExcelList.add(new RefundInfoExcelVO());
        }
        WriteSheet sheetWriter = EasyExcel.writerSheet(type).head(RefundInfoExcelVO.class).build();
        excelWriter.write(refundInfoExcelList, sheetWriter);
    }

    @Override
    public PageVO<ShopAccountDetailVO> pageRefundAccountDetail(PageDTO pageDTO, AccountSearchDTO accountSearchDTO) {
        // 获取店铺信息
        ServerResponseEntity<PageShopAccountOrderVO> orderResponse = orderRefundFeignClient.listShopAccountDetail(accountSearchDTO.getStartTime(), accountSearchDTO.getEndTime(), accountSearchDTO.getShopName(), pageDTO.getPageSize(), pageDTO.getPageNum());
        if (!orderResponse.isSuccess()) {
            throw new LuckException(orderResponse.getMsg());
        }
        List<ShopAccountOrderDetailVO> shopList = orderResponse.getData().getShopList();
        List<ShopAccountDetailVO> result = BeanUtil.mapAsList(shopList, ShopAccountDetailVO.class);

        PageVO<ShopAccountDetailVO> shopPage = new PageVO<>();
        shopPage.setTotal(orderResponse.getData().getTotal());
        shopPage.setList(result);
        shopPage.setPages(PageUtil.getPages(orderResponse.getData().getTotal(), pageDTO.getPageSize()));
        return shopPage;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void doRefundByAmountError(PayInfo payInfo, PayRefundBO payRefundBO) {
        doRefund(payRefundBO);
        // 标记为退款
        PayInfo updatePayInfo = new PayInfo();
        updatePayInfo.setPayId(payInfo.getPayId());
        updatePayInfo.setPayStatus(PayStatus.REFUND.value());
        payInfoMapper.update(updatePayInfo);

        PayNotifyBO message = new PayNotifyBO();
        message.setPayId(payInfo.getPayId());
        message.setPayType(payInfo.getPayType());
        message.setPaySysType(payInfo.getPaySysType());
        // 发送消息，订单支付成功
        // 通知用户余额支付成功
        SendStatus sendBalanceStatus = allinPayBalanceOrderSuccessTemplate.syncSend(RocketMqConstant.ALLINPAY_BALANCE_ORDER_SUCCESS_TOPIC, new GenericMessage<>(message)).getSendStatus();
        if (!Objects.equals(sendBalanceStatus, SendStatus.SEND_OK)) {
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    @Override
    public void updateByIdPayId(RefundInfo refundInfo) {
        refundInfoMapper.updateByIdAndPayId(refundInfo);
    }

    private void getTotal(AccountDetailVO accountDetailVO) {
        Long wechatAmount = Objects.isNull(accountDetailVO.getWechatAmount()) ? 0L : accountDetailVO.getWechatAmount();
        Long alipayAmount = Objects.isNull(accountDetailVO.getAlipayAmount()) ? 0L : accountDetailVO.getAlipayAmount();
        Long balanceAmount = Objects.isNull(accountDetailVO.getBalanceAmount()) ? 0L : accountDetailVO.getBalanceAmount();
        long total = wechatAmount + alipayAmount + balanceAmount;
        accountDetailVO.setTotal(total);
        accountDetailVO.setWechatPercent(Arith.div(wechatAmount.doubleValue(), total, 4));
        accountDetailVO.setAlipayPercent(Arith.div(alipayAmount.doubleValue(), total, 4));
        accountDetailVO.setBalancePercent(Arith.div(balanceAmount.doubleValue(), total, 4));
    }
}
