package com.jeequan.jeepay.thirdparty.channel.wxpay;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.csv.CsvData;
import cn.hutool.core.text.csv.CsvRow;
import cn.hutool.core.text.csv.CsvUtil;
import com.github.binarywang.wxpay.bean.request.WxPayApplyTradeBillV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayDownloadBillRequest;
import com.github.binarywang.wxpay.bean.result.WxPayApplyBillV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayBillInfo;
import com.github.binarywang.wxpay.bean.result.WxPayBillResult;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.CheckBatch;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.bill.ChannelBill;
import com.jeequan.jeepay.core.model.bill.ChannelBillRQ;
import com.jeequan.jeepay.core.model.bill.ChannelBillRS;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.params.wxpay.WxpayIsvParams;
import com.jeequan.jeepay.core.model.params.wxpay.WxpayNormalMchParams;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.db.entity.CheckChannelBill;
import com.jeequan.jeepay.thirdparty.channel.AbstractBillDownloadService;
import com.jeequan.jeepay.thirdparty.model.WxServiceWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/*
* 微信 对账单下载
*
* @author zx
*
* @date 2021/6/8 17:20
*/
@Slf4j
@Service
public class WxpayBillDownloadService extends AbstractBillDownloadService {

    @Override
    public String getIfCode() {
        return CS.IF_CODE.WXPAY;
    }

    @Override
    public ChannelBillRS convertStandardBill(ChannelBillRQ channelBillRQ, MchAppConfigContext mchAppConfigContext, Date billDate, String ifCode){

        String logPrefix = "【微信对账单下载】";

        WxServiceWrapper wxServiceWrapper;

        // true-服务商模式
        boolean isvType = ChannelBillRQ.INFO_TYPE_ISV.equals(channelBillRQ.getInfoType());
        if (isvType) {
            WxpayIsvParams wxpayIsvParams = (WxpayIsvParams) channelBillRQ.getIsvParams();
            wxServiceWrapper = WxServiceWrapper.buildWxServiceWrapper(wxpayIsvParams, null);
        }else {
            WxpayNormalMchParams wxpayNormalMchParams = (WxpayNormalMchParams) channelBillRQ.getNormalMchParams();
            wxServiceWrapper = WxServiceWrapper.buildWxServiceWrapper(wxpayNormalMchParams, null);
        }
        WxPayService wxPayService = wxServiceWrapper.getWxPayService();

        // 渠道商户号
        String channelMchNo = channelBillRQ.getChannelMchNo();

        if (CS.PAY_IF_VERSION.WX_V2.equals(wxServiceWrapper.getApiVersion())) {  // V2
            return convertStandardBillV2(ifCode, wxPayService, isvType, channelMchNo, billDate, logPrefix);
        }else if (CS.PAY_IF_VERSION.WX_V3.equals(wxServiceWrapper.getApiVersion())) {   // V3
            return convertStandardBillV3(ifCode, wxPayService, isvType, channelMchNo, billDate, logPrefix);
        }else {
            throw new BizException("请选择微信V2或V3模式！");
        }
    }

    private ChannelBillRS convertStandardBillV2(String ifCode, WxPayService wxPayService, boolean isvsubMch, String channelMchNo, Date billDate, String logPrefix) {
        ChannelBillRS channelBillRes = new ChannelBillRS();

        WxPayDownloadBillRequest request = new WxPayDownloadBillRequest();
        request.setBillDate(DateUtil.format(billDate, DatePattern.PURE_DATE_PATTERN));
        request.setBillType("ALL");
        request.setTarType(WxPayConstants.TarType.GZIP);

        try {
            // 微信List账单
            WxPayBillResult wxPayBillResult = wxPayService.downloadBill(request);
            List<WxPayBillInfo> billInfoList = wxPayBillResult.getBillInfoList();

            // 上传oss
            byte[] bytes = wxPayService.postForBytes(wxPayService.getPayBaseUrl() + "/pay/downloadbill", request.toXML(), false);
            String url = upload2Oss(getIfCode(), channelMchNo, billDate, ".gzip", new ByteArrayInputStream(bytes));
            /*ByteArrayOutputStream out = new ByteArrayOutputStream();
            ExcelUtil.getBigWriter().write(billInfoList).flush(out);
            String url = upload2Oss(getIfCode(), channelMchNo, billDate, ".csv", new ByteArrayInputStream(out.toByteArray()));*/

            // 生成附带对账文件下载地址的解析成功的批次
            CheckBatch checkBatch = genReleaseSuccessCheckBatch(ifCode, billDate, channelMchNo, url);
            // 渠道账单列表
            List<ChannelBill> channelBillList = new ArrayList<>();
            // 渠道总退款笔数
            int channelTotalRefundCount = 0;

            for (WxPayBillInfo billInfo: billInfoList){
                ChannelBill channelBill = new ChannelBill();
                channelBill.setBatchNo(checkBatch.getBatchNo());
                channelBill.setBillDate(billDate);
                channelBill.setIfCode(getIfCode());
                channelBill.setChannelMchNo(channelMchNo);
                channelBill.setChannelOrderNo(billInfo.getTransactionId());
                channelBill.setChannelSuccessAt(DateUtil.parseDateTime(billInfo.getTradeTime()));
                channelBill.setChannelUser(billInfo.getOpenId());
                // 特约商户  覆写渠道子商户号
                if (isvsubMch) {
                    channelBill.setChannelMchNo(billInfo.getSubMchId());
                }

                if (StringUtils.equals(billInfo.getTradeState(), "SUCCESS")) { // 支付
                    channelBill.setOrderId(billInfo.getOutTradeNo());
                    channelBill.setBillType(CheckChannelBill.BILL_TYPE_PAY);
                    channelBill.setChannelAmount(Math.abs(AmountUtil.convertDollar2CentLong(billInfo.getTotalFee())) + Math.abs(AmountUtil.convertDollar2CentLong(billInfo.getCouponFee())));
                    channelBill.setChannelFeeAmount(Math.abs(AmountUtil.convertDollar2CentLong(billInfo.getPoundage())));
                    channelBill.setChannelState((byte) 2);

                }else if (StringUtils.equals(billInfo.getTradeState(), "REFUND")) { // 退款
                    channelBill.setOrderId(billInfo.getOutRefundNo());
                    channelBill.setBillType(CheckChannelBill.BILL_TYPE_REFUND);
                    channelBill.setChannelAmount(Math.abs(AmountUtil.convertDollar2CentLong(billInfo.getAppliedRefundAmount())));

                    String refundState = billInfo.getRefundState();
                    if (StringUtils.equals(refundState, "SUCCESS")) {
                        channelBill.setChannelState((byte) 5);
                    }
                    channelTotalRefundCount++;
                }else {
                    log.error("{}账单类型错误！", logPrefix);
                    continue;
                }
                channelBillList.add(channelBill);
            }

            checkBatch.setChannelTotalCount(Integer.parseInt(wxPayBillResult.getTotalRecord()) - channelTotalRefundCount);
            checkBatch.setChannelTotalRefundCount(channelTotalRefundCount);
            checkBatch.setChannelTotalAmount(Math.abs(AmountUtil.convertDollar2CentLong(wxPayBillResult.getTotalFee())) + Math.abs(AmountUtil.convertDollar2CentLong(wxPayBillResult.getTotalCouponFee())));
            checkBatch.setChannelTotalRefundAmount(Math.abs(AmountUtil.convertDollar2CentLong(wxPayBillResult.getTotalRefundFee())));
            checkBatch.setChannelTotalFee(Math.abs(AmountUtil.convertDollar2CentLong(wxPayBillResult.getTotalPoundageFee())));

            channelBillRes.setCheckBatch(checkBatch);
            channelBillRes.setChannelBillList(channelBillList);

            return channelBillRes;
        } catch (WxPayException e) {
            log.error("{}, 解析对账文件失败，", logPrefix, e);
            channelBillRes.setCheckBatch(genReleaseFailCheckBatch(ifCode, billDate, channelMchNo, e.getMessage()));
            return channelBillRes;
        }
    }

    private ChannelBillRS convertStandardBillV3(String ifCode, WxPayService wxPayService, boolean isvsubMch, String channelMchNo, Date billDate, String logPrefix) {
        ChannelBillRS channelBillRes = new ChannelBillRS();

        WxPayApplyTradeBillV3Request request = new WxPayApplyTradeBillV3Request();
        request.setBillDate(DateUtil.formatDate(billDate));
        request.setBillType("ALL");

        // 下载地址
        String downloadUrl;
        try {
            // 申请对账单
            WxPayApplyBillV3Result wxPayApplyBillV3Result = wxPayService.applyTradeBill(request);
            downloadUrl = wxPayApplyBillV3Result.getDownloadUrl();
            log.info("{}，微信商户号={}，下载成功", logPrefix, channelMchNo);
        }catch (Exception e) {
            log.error("{}，微信商户号={}，申请对账文件失败", logPrefix, channelMchNo, e);
            channelBillRes.setCheckBatch(genReleaseFailCheckBatch(ifCode, billDate, channelMchNo, e.getMessage()));
            return channelBillRes;
        }

        if (StringUtils.isBlank(downloadUrl)) {
            channelBillRes.setCheckBatch(genReleaseFailCheckBatch(ifCode, billDate, channelMchNo, "下载地址为空！"));
            return channelBillRes;
        }

        // 解析数据
        try {
            InputStream inputStream = wxPayService.downloadBill(downloadUrl);
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            IoUtil.copy(inputStream, byteArrayOutputStream);

            // 上传oss
            String url = upload2Oss(getIfCode(), channelMchNo, billDate, ".csv", IoUtil.toStream(byteArrayOutputStream));
            // 生成附带对账文件下载地址的解析成功的批次
            CheckBatch checkBatch = genReleaseSuccessCheckBatch(ifCode, billDate, channelMchNo, url);
            // 渠道账单列表
            List<ChannelBill> channelBillList = new ArrayList<>();
            // 渠道总退款笔数
            int channelTotalRefundCount = 0;

            // 读取对账单 csv文件
            CsvData data = CsvUtil.getReader(new InputStreamReader(IoUtil.toStream(byteArrayOutputStream))).read();
            List<CsvRow> rows = data.getRows();

            // 遍历行
            if (rows.size() > 2) {
                for (int i = 1; i < rows.size() - 2; i++) {
                    CsvRow csvRow = rows.get(i);
                    // getRawList返回一个List列表，列表的每一项为CSV中的一个单元格
                    List<String> rawList = csvRow.getRawList();

                    if (rawList.size() > 25) {
                        ChannelBill channelBill = new ChannelBill();
                        channelBill.setBatchNo(checkBatch.getBatchNo());
                        channelBill.setBillDate(billDate);
                        channelBill.setIfCode(getIfCode());
                        channelBill.setChannelMchNo(channelMchNo);
                        channelBill.setChannelOrderNo(rawList.get(5).substring(1).trim());
                        channelBill.setChannelFeeAmount(Math.abs(AmountUtil.convertDollar2CentLong(rawList.get(22).substring(1).trim())));
                        channelBill.setChannelSuccessAt(DateUtil.parseDate(rawList.get(0).substring(1).trim()));
                        channelBill.setChannelUser(rawList.get(7).substring(1).trim());
                        // 特约商户  覆写渠道子商户号
                        if (isvsubMch) {
                            channelBill.setChannelMchNo(rawList.get(3).substring(1).trim());
                        }

                        if (StringUtils.equals(rawList.get(9).substring(1).trim(), "SUCCESS")) { // 支付
                            channelBill.setOrderId(rawList.get(6).substring(1).trim());
                            channelBill.setBillType(CheckChannelBill.BILL_TYPE_PAY);
                            channelBill.setChannelAmount(Math.abs(AmountUtil.convertDollar2CentLong(rawList.get(12).substring(1).trim())) + Math.abs(AmountUtil.convertDollar2CentLong(rawList.get(13).substring(1).trim())));
                            channelBill.setChannelState((byte) 2);

                        }else if (StringUtils.equals(rawList.get(9).substring(1).trim(), "REFUND")) { // 退款
                            channelBill.setOrderId(rawList.get(15).substring(1).trim());
                            channelBill.setBillType(CheckChannelBill.BILL_TYPE_REFUND);
                            channelBill.setChannelAmount(Math.abs(AmountUtil.convertDollar2CentLong(rawList.get(25).substring(1).trim())));

                            String refundState = rawList.get(19).substring(1).trim();
                            if (StringUtils.equals(refundState, "SUCCESS")) {
                                channelBill.setChannelState((byte) 5);
                            }
                            channelTotalRefundCount++;
                        }else {
                            log.error("{}账单类型错误！", logPrefix);
                            continue;
                        }

                        channelBillList.add(channelBill);
                    }
                }

                List<String> rawList = rows.get(rows.size() - 1).getRawList();
                checkBatch.setChannelTotalCount(Integer.valueOf(rawList.get(0).substring(1).trim()));
                checkBatch.setChannelTotalRefundCount(channelTotalRefundCount);
                checkBatch.setChannelTotalAmount(Math.abs(AmountUtil.convertDollar2CentLong(rawList.get(1).substring(1).trim())) + Math.abs(AmountUtil.convertDollar2CentLong(rawList.get(3).substring(1).trim())));
                checkBatch.setChannelTotalRefundAmount(Math.abs(AmountUtil.convertDollar2CentLong(rawList.get(6).substring(1).trim())));
                checkBatch.setChannelTotalFee(Math.abs(AmountUtil.convertDollar2CentLong(rawList.get(4).substring(1).trim())));
            }

            channelBillRes.setCheckBatch(checkBatch);
            channelBillRes.setChannelBillList(channelBillList);

            return channelBillRes;

        } catch (WxPayException e) {
            log.error("{}，解析对账文件失败，微信商户号：{}", logPrefix, channelMchNo, e);
            channelBillRes.setCheckBatch(genReleaseFailCheckBatch(ifCode, billDate, channelMchNo, e.getMessage()));
            return channelBillRes;
        }
    }

}
