package com.softwarebr.nunu.web.api;

import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.internal.util.codec.Base64;
import com.brframework.commonweb.annotation.SwaggerIgnore;
import com.brframework.commonweb.exception.HandleException;
import com.brframework.commonweb.utils.RequestResponseUtils;
import com.brframework.commonweb.utils.ServletUtils;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.service.WxPayService;
import com.softwarebr.nunu.config.domain.ConfigService;
import com.softwarebr.nunu.domain.PayConfig;
import com.softwarebr.nunu.entity.pay.PayNotifyLog;
import com.softwarebr.nunu.service.pay.PayNotifyLogService;
import com.softwarebr.nunu.service.pay.ali.AliPayNotifyService;
import com.softwarebr.nunu.service.pay.ali.AliPayService;
import com.softwarebr.nunu.service.pay.weixin.WeiXinPayNotifyService;
import com.softwarebr.nunu.util.SHAUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 支付回调
 * <p>
 * 关于支付宝异步通知：
 * https://opensupport.alipay.com/support/knowCategory/20070
 * <p>
 * 关于支付宝服务端demo
 * https://docs.open.alipay.com/54/106370/
 *
 * @author xu
 * @date 2019/11/1 10:28
 */
@RestController
@Api(tags = "支付回调")
@RequestMapping("/api")
@Slf4j
public class ApiPayNotifyController {

    @Autowired
    ConfigService<PayConfig> configConfigService;

    @Autowired
    AliPayService aliPayService;

    @Autowired
    PayNotifyLogService payNotifyLogService;

    @Autowired(required = false)
    AliPayNotifyService aliPayNotifyService;

    @Autowired(required = false)
    WeiXinPayNotifyService weiXinPayNotifyService;

    @Autowired
    WxPayService wxPayService;

    public static final String  CONSTANT_PAY_CALLBACK_RESULT_SUCCESS = "success";

    @PostMapping("v1/alipay/{client}/notify")
    @ApiOperation(value = "支付宝APP支付通知", notes = "支付宝APP支付通知")
    public void alipayAppNotifyV1(@PathVariable String client) {
        String outTradeNo = "";
        String notifyType = "";
        String result = "";
        String content = "";

        try {
            PayConfig payConfig = configConfigService.get();
            //获取支付宝POST过来反馈信息
            Map<String, String> params = new HashMap<>();
            Map requestParams = ServletUtils.request().getParameterMap();
            for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
                String name = (String) iter.next();
                String[] values = (String[]) requestParams.get(name);
                String valueStr = "";
                for (int i = 0; i < values.length; i++) {
                    valueStr = (i == values.length - 1) ? valueStr + values[i]
                            : valueStr + values[i] + ",";
                }
                //乱码解决，这段代码在出现乱码时使用。
                //valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
                params.put(name, valueStr);
            }



            //切记alipaypublickey是支付宝的公钥，请去open.alipay.com对应应用下查看。
            //boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
            boolean flag = false;
            if(client.equals("APP")){
                flag = AlipaySignature.rsaCheckV1(params, aliSign(payConfig.getAppApAlipayPublicCertContent()), "utf-8", "RSA2");
            }
            if(!flag) throw new HandleException("验签失败");

            outTradeNo = params.get("out_trade_no");
            if(StringUtils.isEmpty(outTradeNo)){
                outTradeNo = params.get("out_request_no");
            }

            notifyType = params.get("notify_type");
            params.remove("sign");
            content = JSON.toJSONString(params);
            log.info("支付宝回调信息：" + content);

            final String type = params.get("notify_type");


            switch (type) {
                //交易异步状态通知
                case "trade_status_sync":

                    String status = params.get("trade_status");
                    if(status.equals("TRADE_SUCCESS")){
                        notifyType = notifyType + ".TRADE_SUCCESS";
                        //支付成功
                        aliPayNotifyService.tradeSuccess(outTradeNo, params.get("buyer_id"), params.get("buyer_pay_amount"));

                    } else if(status.equals("TRADE_CLOSED")){
                        notifyType = notifyType + ".TRADE_CLOSED";
                        //交易关闭
                        aliPayNotifyService.tradeClose(outTradeNo);
                    }
                    break;
                default:
                    throw new HandleException("未知通知类型：" + type);
            }

            RequestResponseUtils.writerJson(ServletUtils.response(), "success");
            result = CONSTANT_PAY_CALLBACK_RESULT_SUCCESS;
        } catch (Throwable e) {
            log.error("支付通知失败", e);
            RequestResponseUtils.writerJson(ServletUtils.response(), "error");
            result = ExceptionUtils.getStackTrace(e);
        }

        payNotifyLogService.save(PayNotifyLog.builder()
                .createDate(LocalDateTime.now())
                .platform("alipay-" + client)
                .outTradeNo(outTradeNo)
                .notifyType(notifyType)
                .notifyResult(result)
                .content(content)
                .build());
    }


    private static String aliSign(String alipayPublicKey) throws AlipayApiException {

        ByteArrayInputStream inputStream = null;
        try {
            inputStream = new ByteArrayInputStream(alipayPublicKey.getBytes());
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            PublicKey publicKey = cert.getPublicKey();
            return Base64.encodeBase64String(publicKey.getEncoded());
        } catch (CertificateException e) {
            throw new AlipayApiException(e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                throw new AlipayApiException(e);
            }
        }


    }

    @PostMapping("v1/weixin/{client}/notify")
    @ApiOperation(value = "微信APP支付通知", notes = "微信APP支付通知")
    public String weixinPayAppNotifyV1(@PathVariable String client) {
        //订单号
        String outTradeNo = "";
        //通知类型
        String notifyType = "";
        //结果
        String result;
        //通知内容
        String content = "";
        String returnResult;
        log.info("进入微信支付异步通知");
        try {
            ServletInputStream inputStream = ServletUtils.request().getInputStream();
            //将InputStream转换成String
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
            WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(sb.toString());
            content = JSON.toJSONString(notifyResult);
            outTradeNo = notifyResult.getOutTradeNo();
            weiXinPayNotifyService.tradeSuccess(outTradeNo, notifyResult.getOpenid(), new BigDecimal(notifyResult.getTotalFee()).divide(new BigDecimal(100)).toString());
            result = CONSTANT_PAY_CALLBACK_RESULT_SUCCESS;
            notifyType = "trade_status_sync.TRADE_SUCCESS";
            returnResult = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[SUCCESS]]></return_msg>" + "</xml> ";
        } catch (Throwable e) {
            log.error("支付通知失败", e);
            RequestResponseUtils.writerJson(ServletUtils.response(), "error");
            result = ExceptionUtils.getStackTrace(e);
            returnResult = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
        }
        payNotifyLogService.save(PayNotifyLog.builder()
                .createDate(LocalDateTime.now())
                .platform("weixin-" + client)
                .outTradeNo(outTradeNo)
                .notifyType(notifyType)
                .notifyResult(result)
                .content(content)
                .build());
        return returnResult;
    }

    @GetMapping("v1/weixin/check/notify")
    @ApiOperation(value = "微信公众号验证通知", notes = "微信公众号验证通知")
    public void weChatCheckV1(HttpServletRequest request, HttpServletResponse response) {
        log.info("开始校验签名...");
        // 接收微信服务器发送请求时传递过来的4个参数
        String signature = request.getParameter("signature");
        //微信加密签名signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
        PayConfig payConfig = configConfigService.get();
        //排序
        String sortString = SHAUtils.sort(payConfig.getWxToken(), timestamp, nonce);
        //加密
        String mySignature = SHAUtils.sha1(sortString);

        //校验签名
        if (mySignature != null && mySignature != "" && mySignature.equals(signature)) {
            log.info("签名校验通过。");
            try {
                response.getWriter().write(echostr);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            log.info("签名校验失败。");
        }

    }

}
