package com.ruoyi.framework.web.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;

import com.github.wxpay.sdk.WXPayUtil;

import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.QRCodeUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.sign.Base64;
import com.ruoyi.framework.web.domain.server.Sys;
import com.ruoyi.system.domain.Customer;
import com.ruoyi.system.domain.Order;
import com.ruoyi.system.domain.OrderOperationLog;
import com.ruoyi.system.domain.OrderRefund;
import com.ruoyi.system.mapper.CustomerMapper;

import com.ruoyi.system.mapper.OrderMapper;
import com.ruoyi.system.mapper.OrderRefundMapper;
import com.ruoyi.system.service.impl.MachineApiServiceImpl;
import com.ruoyi.system.service.impl.OrderOperationLogServiceImpl;
import com.ruoyi.system.weixin.entity.OrderInfo;

import com.ruoyi.system.weixin.entity.QueryReturnInfo;

import com.ruoyi.system.weixin.util.RandomStringGenerator;
import com.ruoyi.system.weixin.util.Signature;
import com.thoughtworks.xstream.XStream;

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.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.security.KeyStore;
import java.security.spec.AlgorithmParameterSpec;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class WeixinServiceImpl  {

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

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private CustomerMapper customerMapper;
    @Autowired
    private TokenService tokenService;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderOperationLogServiceImpl orderOperationLogService;
    @Autowired
    private MachineApiServiceImpl machineApiService;
    @Autowired
    private OrderRefundMapper orderRefundMapper;

    private final ReentrantLock lock = new ReentrantLock();


    @Value("${weixin.appid}")
    private String appid;

    @Value("${weixin.secret}")
    private String secret;
    @Value("${weixin.mchid}")
    private String mchId;
    @Value("${weixin.key}")
    private String key;
    @Value("${weixin.notify-url}")
    private String notifyUrl;
    @Value("${weixin.trade-type}")
    private String tradeType;
    @Value("${weixin.url}")
    private String url;
    @Value("${weixin.query-url}")
    private String queryUrl;

    @Value("${weixin.cert-path}")
    private String certPath;
    @Value("${weixin.refund-url}")
    private String refundUrl;

    public String getSessionKeyOrOpenId(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";
        String replaceUrl = url.replace("{0}", appid).replace("{1}", secret).replace("{2}", code);
        String res = restTemplate.getForObject(replaceUrl,String.class);
        return res;
    }

    public String  login(String code, String encryptedData, String iv, String source) {
        try{
            String sessionKeyOrOpenId = getSessionKeyOrOpenId(code);
            JSONObject openInfo = JSONObject.parseObject(sessionKeyOrOpenId);
            String cutomerId = openInfo.getString("openid");
            String encData = this.wxDecrypt(encryptedData, sessionKeyOrOpenId, iv);
            log.info("当前用户的信息为：【{}】",encData);
            Customer customer = customerMapper.selectCustomerById(cutomerId);
            JSONObject encDataInf = JSONObject.parseObject(encData);
            if(customer ==null){
                customer = new Customer();
                customer.setId(cutomerId);
                customer.setHeadSculpturePath(encDataInf.getString("avatarUrl"));
                customer.setUserName(encDataInf.getString("nickName"));
                customer.setPhone(encDataInf.getString("phoneNumber"));
                customer.setCreateTime(new Date());
                if(StringUtils.isNotEmpty(source)){
                    customer.setSoucre(source);
                }
                customerMapper.insertCustomer(customer);
            }

            return tokenService.createToken(transLoginUser(customer,encData));
        }catch (Exception e){
            log.error("微信登录发生异常",e);
            throw new ServiceException("微信登录异常，请重新登录");
        }


    }
    private LoginUser transLoginUser(Customer customer,String encData){
        SysUser user = new SysUser();
        user.setUserName(customer.getUserName());
        LoginUser loginUser = new LoginUser(user,null);
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setOpenId(customer.getId());
        loginUser.setExt(encData);


        return loginUser;

    }

    public String wxDecrypt(String encryptedData, String json, String vi) throws Exception {
        // 开始解密
        //：{"session_key":"G59Evf\/Em54X6WsFsrpA1g==","openid":"o2ttv5L2yufc4-VoSPhTyUnToY60"}
        JSONObject jsonObject = JSON.parseObject(json);
        String sessionKey = (String) jsonObject.get("session_key");
        byte[] encData = Base64.decode(encryptedData);
        byte[] iv = Base64.decode(vi);
        byte[] key = Base64.decode(sessionKey);
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return new String(cipher.doFinal(encData), "UTF-8");
    }












    public Map orderQuery() {
        Map<String, Object> map = new HashMap<>();

        try {
            OrderInfo order = new OrderInfo();
            order.setAppid(appid);
            order.setMch_id(mchId);
            order.setNonce_str(RandomStringGenerator.getRandomStringByLength(32));
            order.setOut_trade_no("wuyye9w8akwbq1nc60o4affwwqnxh9dn");
            //order.setSign_type("MD5");
            //生成签名
            String sign = Signature.getSign(order,key);
            order.setSign(sign);
            String result = restTemplate.postForObject(queryUrl,order,String.class);
            System.out.println(result);
            XStream xStream = new XStream();
            xStream.alias("xml", QueryReturnInfo.class);

            QueryReturnInfo returnInfo = (QueryReturnInfo) xStream.fromXML(result);
            map.put("status", 500);
            map.put("msg", "统一下单失败!");
            map.put("data", returnInfo);
            return map;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Autowired
    private WxPayService wxPayService;

    public Map<String, String> weChatPay(HttpServletRequest r,String body,String orderNo,String openId,int money) {
        /**
         * 处理内部业务，校验订单等
         */
        final WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = WxPayUnifiedOrderRequest.newBuilder()
                //调起支付的人的 openId
                .openid(openId)
                //订单编号
                .outTradeNo(orderNo)
                //订单金额
                .totalFee(money)
                //商品描述
                .body("订单信息")
                //获取本地IP
                .spbillCreateIp(InetAddress.getLoopbackAddress().getHostAddress())
                //回调的 URL 地址
                .notifyUrl("http://我们的域名/api/client/pay/weChatPayNotify")
                .build();
        WxPayUnifiedOrderResult wxPayUnifiedOrderResult =null;
        try {
            wxPayUnifiedOrderResult = wxPayService.unifiedOrder(wxPayUnifiedOrderRequest);
        } catch (WxPayException e) {
            e.printStackTrace();
            throw new RuntimeException("微信支付调起失败！");
        }
        return null;
    }

    public String refund(String orderNo,String refundNo,Integer totalFee,Integer refundFee) {
        //申请退款
        WxPayRefundRequest refundInfo = WxPayRefundRequest.newBuilder()
                //订单号
                .outTradeNo(orderNo)
                //退款订单号
                .outRefundNo(refundNo)
                //金额
                .totalFee(totalFee)
                //退款金额
                .refundFee(refundFee)
                //todo 回调地址
                .notifyUrl(refundUrl)
                .build();
        WxPayRefundResult wxPayRefundResult;
        try {
            wxPayRefundResult = wxPayService.refund(refundInfo);
            //判断退款信息是否正确
            if ("SUCCESS".equals(wxPayRefundResult.getReturnCode())) {
                /**
                 * 系统内部业务逻辑
                 */
                return "正在退款中。。";
            }
        } catch (WxPayException e) {
            log.error("微信退款接口错误信息= {}", e);
            throw new ServiceException("微信退款失败");
        }

        return "退款失败";
    }






    public  Map<String, String> pay(HttpServletRequest r,String body,String orderNo,String openId,int money) {
        try{
            HttpClientUtils client = new HttpClientUtils(url);
            //组装接口参数
            Map<String, String> params = new HashMap<>();
            params.put("appid", appid);//关联的公众号的appid
            params.put("mch_id", mchId);//商户号
            params.put("nonce_str", WXPayUtil.generateNonceStr());//生成随机字符串
            params.put("body", body);
            params.put("out_trade_no", orderNo);
            params.put("openid", openId);
            //注意，这里必须使用字符串类型的参数（总金额：分）

            params.put("total_fee", String.valueOf(money));

            params.put("spbill_create_ip", IpUtils.getIpAddr(r));
            params.put("notify_url", notifyUrl);
            params.put("trade_type", "JSAPI");

            //将参数转换成xml字符串格式：生成带有签名的xml格式字符串
            String xmlParams = WXPayUtil.generateSignedXml(params, key);
            log.info("\n xmlParams：\n" + xmlParams);

            client.setXmlParam(xmlParams);//将参数放入请求对象的方法体
            client.setHttps(true);//使用https形式发送
            client.post();//发送请求
            String resultXml = client.getContent();//得到响应结果
            log.info("\n resultXml：\n" + resultXml);
            //将xml响应结果转成map对象
            Map<String, String> resultMap = WXPayUtil.xmlToMap(resultXml);

            //错误处理
            if("FAIL".equals(resultMap.get("return_code")) || "FAIL".equals(resultMap.get("result_code"))){
                log.error("微信支付统一下单错误 ===> {} ", resultXml);
                throw new RuntimeException("微信支付统一下单错误");
            }
            long time = System.currentTimeMillis() / 1000;
            Map<String,String> signInfo =new HashMap<>();
            signInfo.put("appId",appid);
            signInfo.put("timeStamp",String.valueOf(time));
            signInfo.put("nonceStr",WXPayUtil.generateNonceStr());
            signInfo.put("repay_id","prepay_id=" + resultMap.get("prepay_id"));
            signInfo.put("signType","MD5");
            //生成签名


            Map<String, String> payInfo = new HashMap<>();
            payInfo.put("appId",appid);
            payInfo.put("timeStamp", signInfo.get("timeStamp"));
            payInfo.put("nonceStr", signInfo.get("nonceStr"));
            payInfo.put("package", signInfo.get("repay_id"));
            payInfo.put("signType", "MD5");
            String sign1 = WXPayUtil.generateSignature(payInfo, key);
            payInfo.put("paySign", sign1);
            // 此处可以写唤起支付前的业务逻辑
            // 业务逻辑结束 回传给小程序端唤起支付
            return payInfo;
        }catch (Exception e){
            log.error("微信支付发生异常",e);
            throw new ServiceException("微信支付异常");
        }



    }


    public String wxNotify(HttpServletRequest request) throws Exception {
        Map<String, String> returnMap = new HashMap<>();//应答对象

        //处理通知参数
        String body = this.readData(request);

        //验签
        if(!WXPayUtil.isSignatureValid(body, key)) {
            log.error("通知验签失败");
            //失败应答
            returnMap.put("return_code", "FAIL");
            returnMap.put("return_msg", "验签失败");
            String returnXml = WXPayUtil.mapToXml(returnMap);
            return returnXml;
        }

        //解析xml数据
        Map<String, String> notifyMap = WXPayUtil.xmlToMap(body);
        //判断通信和业务是否成功
        if(!"SUCCESS".equals(notifyMap.get("return_code")) || !"SUCCESS".equals(notifyMap.get("result_code"))) {
            log.error("失败");
            //失败应答
            returnMap.put("return_code", "FAIL");
            returnMap.put("return_msg", "失败");
            String returnXml = WXPayUtil.mapToXml(returnMap);
            return returnXml;
        }

        //获取商户订单号
        String orderNo = notifyMap.get("out_trade_no");
        Order order = new Order();
        order.setOrderNo(orderNo);
        Order order1 = orderMapper.selectOrderList(order).get(0);
        //并校验返回的订单金额是否与商户侧的订单金额一致
//        if (order1 != null && order1.get() != Long.parseLong(notifyMap.get("total_fee"))) {
//            log.error("金额校验失败");
//            //失败应答
//            returnMap.put("return_code", "FAIL");
//            returnMap.put("return_msg", "金额校验失败");
//            String returnXml = WXPayUtil.mapToXml(returnMap);
//            return returnXml;
//        }

        //处理订单
        if(lock.tryLock()){
            try {
                //处理重复的通知
                //接口调用的幂等性：无论接口被调用多少次，产生的结果是一致的。
                String orderStatus =order1.getState();
                if("1".equals(orderStatus)){
                    //更新订单状态
                    order1.setPayTime(new Date());
                    order1.setState("2");
                    JSONObject jsonObject =new JSONObject();
                    jsonObject.put("orderId",order.getId());
                    jsonObject.put("userId",order.getUserId());
                    order1.setPickCode(QRCodeUtil.getBase64QRCode(jsonObject.toJSONString()));
                    orderMapper.updateOrder(order1);
                    OrderOperationLog orderOperationLog = new OrderOperationLog();
                    orderOperationLog.setOrderId(String.valueOf(order.getId()));
                    orderOperationLog.setOperation("付款");
                    orderOperationLog.setOperationUser(order.getUserName());
                    orderOperationLog.setCreateAt(new Date());
                    orderOperationLog.setContent("付款成功");
                    orderOperationLog.setStatus("付款");
                    orderOperationLogService.insertOrderOperationLog(orderOperationLog);
                    machineApiService.updateOrder(order1);


                }
            } finally {
                //要主动释放锁
                lock.unlock();
            }
        }
        returnMap.put("return_code", "SUCCESS");
        returnMap.put("return_msg", "OK");
        String returnXml = WXPayUtil.mapToXml(returnMap);
        log.info("支付成功，已应答");
        return returnXml;
    }
    /**
     * 将通知参数转化为字符串
     * @param request
     * @return
     */
    public  String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public String refundNotify(String xmlData) {
        WxPayRefundNotifyResult wxPayRefundNotifyResult;
        try {
            wxPayRefundNotifyResult = wxPayService.parseRefundNotifyResult(xmlData);
        } catch (WxPayException e) {
            log.error("退款失败，失败信息:{}", e);
            return WxPayNotifyResponse.fail("退款失败");
        }
        //判断你返回状态信息是否正确
        if ("SUCCESS".equals(wxPayRefundNotifyResult.getReturnCode())) {
            WxPayRefundNotifyResult.ReqInfo reqInfo = wxPayRefundNotifyResult.getReqInfo();
            String outRefundNo = reqInfo.getOutRefundNo();
            OrderRefund orderRefund = new OrderRefund();
            orderRefund.setRefundNo(outRefundNo);
            List<OrderRefund> orderRefunds = orderRefundMapper.selectOrderRefundList(orderRefund);
            if(orderRefunds.isEmpty()){
                log.error("找不到该退款订单：【{}】",outRefundNo);
                return WxPayNotifyResponse.fail("退款失败");
            }
            OrderRefund orderRefund1 = orderRefunds.get(0);
            if("1".equals(orderRefund1.getState())){
                orderRefund1.setState("2");
                orderRefundMapper.updateOrderRefund(orderRefund1);
                OrderOperationLog orderOperationLog = new OrderOperationLog();
                orderOperationLog.setOrderId(String.valueOf(orderRefund1.getOrderId()));
                orderOperationLog.setOperation("退款");
                orderOperationLog.setOperationUser("自动退款");
                orderOperationLog.setCreateAt(new Date());
                orderOperationLog.setContent("退款成功");
                orderOperationLog.setStatus("退款");
                orderOperationLogService.insertOrderOperationLog(orderOperationLog);
                return WxPayNotifyResponse.success("退款成功！");
            }


            //判断退款状态
//            if (REFUND_SUCCESS.equals(reqInfo.getRefundStatus())) {
//                //内部订单号
//                String outTradeNo = reqInfo.getOutTradeNo();
//                /**
//                 * 一、可能会重复回调，需要做防重判断
//                 * 二、处理我们系统内部业务，做修改订单状态，释放资源等！
//                 */
//                return WxPayNotifyResponse.success("退款成功！");
//            }
        }
        return WxPayNotifyResponse.fail("回调有误!");
    }
}
