package com.liquidnet.service.dragon.service.impl;

import com.alibaba.fastjson.JSON;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.commons.lang.util.IDGenerator;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.dragon.constant.DragonConstant;
import com.liquidnet.service.dragon.dto.DragonRefundAppDto;
import com.liquidnet.service.dragon.dto.DragonRefundChannelDto;
import com.liquidnet.service.dragon.dto.RefundContentDto;
import com.liquidnet.service.dragon.channel.wepay.resp.WePayRefundReturnDto;
import com.liquidnet.service.dragon.service.IDragonOrderRefundsService;
import com.liquidnet.service.dragon.utils.PayAlipayUtils;
import com.liquidnet.service.dragon.utils.PayWepayUtils;
import com.liquidnet.service.dragon.utils.XmlUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.StreamRecords;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;

@Slf4j
@Service
public class DragonOrderRefundsServiceImpl implements IDragonOrderRefundsService {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Override
    public void sendRedisQueue() {
    }

    @Override
    public ResponseDto<DragonRefundAppDto> dragonRefund(String orderCode, String code, String orderRefundCode, String reason, String notifyUrl, BigDecimal price, String paymentType, String paymentId, BigDecimal priceTotal) {
        try {
            LocalDateTime nowTime = LocalDateTime.now();
            String refundCode = IDGenerator.refundCode();
            String orderRefundId = IDGenerator.nextSnowId();
            //创建退款单
            boolean insertResult = sendMySqlRedis(
                    SqlMapping.get("dragon_order_refund.insert"),
                    new Object[]{orderRefundId, refundCode, orderRefundCode, price, reason, notifyUrl, paymentType, nowTime, nowTime}
            );
            DragonRefundChannelDto dto = null;
            if (insertResult) {
                switch (paymentType) {
                    case DragonConstant.REFUND_TYPE_APP_ALIPAY:
                        dto = aliPayRefund(orderRefundId, orderRefundCode, code, reason, price, paymentId, paymentType, nowTime);
                        break;
                    case DragonConstant.REFUND_TYPE_WAP_ALIPAY:
                        dto = aliPayRefund(orderRefundId, orderRefundCode, code, reason, price, paymentId, paymentType, nowTime);
                        break;
                    case DragonConstant.REFUND_TYPE_WEB_ALIPAY:
                        dto = aliPayRefund(orderRefundId, orderRefundCode, code, reason, price, paymentId, paymentType, nowTime);
                        break;
                    case DragonConstant.REFUND_TYPE_APP_WEPAY:
                        dto = weyPayRefund(orderRefundId, orderRefundCode, code, reason, price, priceTotal, paymentId, paymentType, notifyUrl, nowTime);
                        break;
                    case DragonConstant.REFUND_TYPE_WAP_WEPAY:
                        dto = weyPayRefund(orderRefundId, orderRefundCode, code, reason, price, priceTotal, paymentId, paymentType, notifyUrl, nowTime);
                        break;
                    case DragonConstant.REFUND_TYPE_WEB_WEPAY:
                        dto = weyPayRefund(orderRefundId, orderRefundCode, code, reason, price, priceTotal, paymentId, paymentType, notifyUrl, nowTime);
                        break;
                    case DragonConstant.REFUND_TYPE_JS_WEPAY:
                        dto = weyPayRefund(orderRefundId, orderRefundCode, code, reason, price, priceTotal, paymentId, paymentType, notifyUrl, nowTime);
                        break;
                }

                if (dto.getResult().equalsIgnoreCase("refunding")) {
                    log.info("");
                } else if (dto.getResult().equalsIgnoreCase("refunded")) {
                    //调用回调
                    log.info("");
                } else if (dto.getResult().equalsIgnoreCase("error")) {
                    //调用回调
                    log.error("");
                } else if (dto.getResult().equalsIgnoreCase("exception")) {
                    log.error("");
                }


                DragonRefundAppDto refundAppDto = new DragonRefundAppDto();
                refundAppDto.setOrderCode(orderCode);
                refundAppDto.setCode(code);
                refundAppDto.setOrderRefundCode(orderRefundCode);
                refundAppDto.setRefundCode(refundCode);
                return ResponseDto.success(refundAppDto);
            } else {
                return ResponseDto.failure("退款失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("");
            return ResponseDto.failure("退款失败:" + e.getMessage());
        }
    }

    @Override
    public ResponseDto<DragonRefundAppDto> wePayRefundCallBack() {
        return null;
    }

    public DragonRefundChannelDto weyPayRefund(String orderRefundId, String refundCode, String code, String reason, BigDecimal price, BigDecimal priceTotal, String paymentId, String paymentType, String notifyUrl, LocalDateTime nowTime) {
        try {
            String refundStatus = DragonConstant.RefundStatusEnum.STATUS_ERROR.getCode();
            DragonRefundChannelDto channelDto = new DragonRefundChannelDto();
            RefundContentDto contentDto = new RefundContentDto();
            String nonceStr = PayWepayUtils.getInstance().getNonceStr();
            SortedMap<Object, Object> parameters = new TreeMap<>();
            parameters.put("mch_id", PayWepayUtils.getInstance().getMerchantId());
            parameters.put("appid", PayWepayUtils.getInstance().getAppId());
            parameters.put("nonce_str", nonceStr);
            parameters.put("out_refund_no", refundCode);
            parameters.put("out_trade_no", code);
            parameters.put("refund_fee", price.doubleValue() * 100);
            parameters.put("total_fee", priceTotal.doubleValue() * 100);
            parameters.put("notify_url", notifyUrl);
            parameters.put("refund_desc", reason);
            String sign = PayWepayUtils.getInstance().createSign(parameters);
            parameters.put("sign", sign);
            String data = PayWepayUtils.getInstance().getRequestXml(parameters);
            try {
                HttpPost httpost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund");
                httpost.setEntity(new StringEntity(data, "UTF-8"));
                CloseableHttpResponse response = PayWepayUtils.getInstance().getHttpClient().execute(httpost);
                try {
                    HttpEntity entity = response.getEntity();
                    entity.getContent();
                    String jsonStr = EntityUtils.toString(entity, "UTF-8");
                    WePayRefundReturnDto wePayRefundReturnDto = XmlUtil.toBean(jsonStr, WePayRefundReturnDto.class);

                    if (!wePayRefundReturnDto.getResultCode().equalsIgnoreCase("SUCCESS") || wePayRefundReturnDto.getResultCode() == null) {
                        try {
                            String refundError = "";
                            refundStatus = DragonConstant.RefundStatusEnum.STATUS_ERROR.getCode();
                            if (null == wePayRefundReturnDto.getReturnMsg()) {
                                refundError = "退款失败，原因未知";
                            } else {
                                refundError = wePayRefundReturnDto.getReturnMsg();
                            }
                            // 修改退款订单
                            sendMySqlRedis(
                                    SqlMapping.get("dragon_order_refund_error.update"),
                                    new Object[]{nowTime, refundError, refundStatus, orderRefundId}
                            );
                        } catch (Exception e) {
                            e.printStackTrace();
                            //保存错误信息
                            log.error("");
                            channelDto.setResult("exception");
                            channelDto.setMessage("update order refund with db error: " + e.getMessage());
                            contentDto.setRequest(data);
                            contentDto.setResponse(jsonStr);
                            channelDto.setContent(contentDto);
                            return channelDto;
                        }
                        channelDto.setResult("error");
                        channelDto.setMessage(paymentType + " refund error: ");
                        contentDto.setRequest(data);
                        contentDto.setResponse(jsonStr);
                        channelDto.setContent(contentDto);
                        return channelDto;
                    }

                    // 创建退款日志
                    sendMySqlRedis(
                            SqlMapping.get("dragon_order_refund_log.insert"),
                            new Object[]{orderRefundId, paymentType, data, nowTime, nowTime}
                    );
                    try {
                        if (wePayRefundReturnDto.getResultCode().equalsIgnoreCase("SUCCESS")) {
                            refundStatus = DragonConstant.RefundStatusEnum.STATUS_REFUNDED.getCode();
                        }
                        sendMySqlRedis(
                                SqlMapping.get("dragon_order_refund_success.update"),
                                new Object[]{nowTime, null, refundStatus, orderRefundId}
                        );
                    } catch (Exception e) {
                        e.printStackTrace();
                        log.error("");
                        channelDto.setResult("exception");
                        channelDto.setMessage("update order refund with db error: " + e.getMessage());
                        contentDto.setRequest(data);
                        contentDto.setResponse(jsonStr);
                        channelDto.setContent(contentDto);
                        return channelDto;
                    }
                    channelDto.setResult("refunded");
                    channelDto.setMessage(paymentType + " refund info: ");
                    contentDto.setRequest(data);
                    contentDto.setResponse(jsonStr);
                    channelDto.setContent(contentDto);
                    EntityUtils.consume(entity);
                    return channelDto;
                } finally {
                    response.close();
                }
            } finally {
                PayWepayUtils.getInstance().getHttpClient().close();
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("");
            return null;
        }
    }

    public DragonRefundChannelDto aliPayRefund(String orderRefundId, String refundCode, String code, String reason, BigDecimal price, String paymentId, String paymentType, LocalDateTime nowTime) {
        String refundStatus;
        DragonRefundChannelDto channelDto = new DragonRefundChannelDto();
        RefundContentDto contentDto = new RefundContentDto();
        AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();//创建API对应的request类
        try {
            request.setBizContent("{" +
                    "\"out_trade_no\":\"" + code + "\"," +
                    "\"trade_no\":\"" + paymentId + "\"," +
                    "\"out_request_no\":\"" + refundCode + "\"," +
                    "\"refund_reason\":\"" + reason + "\"," +
                    "\"refund_amount\":\"" + price.doubleValue() + "\"}"); //设置业务参数
            AlipayTradeRefundResponse response = PayAlipayUtils.getInstance().getHttpClient().execute(request);
            if (response.getFundChange().equals("N") || response.getFundChange() == null) {
                try {
                    String refundError = "";
                    refundStatus = DragonConstant.RefundStatusEnum.STATUS_ERROR.getCode();
                    if (null == response.getSubMsg()) {
                        refundError = "退款失败，原因未知";
                    } else {
                        refundError = response.getSubMsg();
                    }
                    // 修改退款订单
                    sendMySqlRedis(
                            SqlMapping.get("dragon_order_refund_error.update"),
                            new Object[]{nowTime, refundError, refundStatus, orderRefundId}
                    );
                } catch (Exception e) {
                    e.printStackTrace();
                    //保存错误信息
                    log.error("");
                    channelDto.setResult("exception");
                    channelDto.setMessage("update order refund with db error: " + e.getMessage());
                    contentDto.setRequest(JSON.toJSONString(response.getParams()));
                    contentDto.setResponse(response.getBody());
                    channelDto.setContent(contentDto);
                    return channelDto;
                }
                channelDto.setResult("error");
                channelDto.setMessage(paymentType + " refund error: ");
                contentDto.setRequest(JSON.toJSONString(response.getParams()));
                contentDto.setResponse(response.getBody());
                channelDto.setContent(contentDto);
                return channelDto;
            }

            // 创建退款日志
            sendMySqlRedis(
                    SqlMapping.get("dragon_order_refund_log.insert"),
                    new Object[]{orderRefundId, paymentType, JSON.toJSONString(response.getBody()), nowTime, nowTime}
            );
            try {
                String refundAt = "";
                if (response.getFundChange().equals("Y")) {
                    refundStatus = DragonConstant.RefundStatusEnum.STATUS_REFUNDED.getCode();
                    refundAt = DateUtil.format(response.getGmtRefundPay(), DateUtil.Formatter.yyyyMMddHHmmss);
                } else {
                    refundStatus = DragonConstant.RefundStatusEnum.STATUS_REFUNDING.getCode();
                }
                sendMySqlRedis(
                        SqlMapping.get("dragon_order_refund_success.update"),
                        new Object[]{nowTime, refundAt, refundStatus, orderRefundId}
                );
            } catch (Exception e) {
                e.printStackTrace();
                log.error("");
                channelDto.setResult("exception");
                channelDto.setMessage("update order refund with db error: " + e.getMessage());
                contentDto.setRequest(JSON.toJSONString(response.getParams()));
                contentDto.setResponse(response.getBody());
                channelDto.setContent(contentDto);
                return channelDto;
            }
            channelDto.setResult("refunded");
            channelDto.setMessage(paymentType + " refund info: ");
            contentDto.setRequest(JSON.toJSONString(response.getParams()));
            contentDto.setResponse(response.getBody());
            channelDto.setContent(contentDto);
            return channelDto;

        } catch (Exception e) {
            e.printStackTrace();
            log.error("");
            return null;
        }
    }

    /**
     * 给 REDIS 队列发送消息 数据库相关
     *
     * @param sql  sql语句
     * @param data 需要操作的数据
     * @return
     */
    private boolean sendMySqlRedis(String sql, Object[] data) {
        try {
            LinkedList<String> sqls = new LinkedList<>();
            sqls.add(sql);
            LinkedList<Object[]> sqlsData = new LinkedList();
            sqlsData.add(data);
            String sqlData = SqlMapping.gets(sqls, sqlsData);

            HashMap<String, String> map = new HashMap<>();
            map.put("message", sqlData);
            MapRecord<String, String, String> record = StreamRecords.mapBacked(map).withStreamKey(DragonConstant.MysqlRedisQueueEnum.DRAGON_REFUND_KEY.getCode());
            stringRedisTemplate.opsForStream().add(record);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}
