package com.xinelu.applet.service.wechatpaymentinfo.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.xinelu.applet.service.wechatpaymentinfo.WeChatPaymentService;
import com.xinelu.applet.utils.WeChatUtil;
import com.xinelu.applet.vo.wechatpaymentinfo.dto.PaymentDTO;
import com.xinelu.applet.vo.wechatpaymentinfo.vo.OrderStatusInfoVO;
import com.xinelu.applet.vo.wechatpaymentinfo.vo.WeChatAppletSignVO;
import com.xinelu.applet.vo.wechatpaymentinfo.vo.WeChatQueryOrderVO;
import com.xinelu.common.config.AppletChatConfig;
import com.xinelu.common.config.NurseAppletChatConfig;
import com.xinelu.common.config.WeChatPaymentUrlConfig;
import com.xinelu.common.config.XylWeChatPaymentConfig;
import com.xinelu.common.constant.Constants;
import com.xinelu.common.core.domain.AjaxResult;
import com.xinelu.common.enums.*;
import com.xinelu.common.exception.ServiceException;
import com.xinelu.manage.domain.appointmentorder.AppointmentOrder;
import com.xinelu.manage.domain.goodsOrder.GoodsOrder;
import com.xinelu.manage.domain.trainingorder.TrainingOrder;
import com.xinelu.manage.mapper.appointmentorder.AppointmentOrderMapper;
import com.xinelu.manage.mapper.goodsOrder.GoodsOrderMapper;
import com.xinelu.manage.mapper.trainingorder.TrainingOrderMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Description 微信小程序和App支付业务层实现类
 * @Author 纪寒
 * @Date 2022-10-18 15:02:40
 * @Version 1.0
 */
@Service
@Slf4j
public class WeChatPaymentServiceImpl implements WeChatPaymentService {

    @Resource
    private GoodsOrderMapper goodsOrderMapper;
    @Resource
    private AppointmentOrderMapper appointmentOrderMapper;

    @Resource
    private TrainingOrderMapper trainingOrderMapper;
    @Resource
    private AppletChatConfig appletChatConfig;
    @Resource
    private XylWeChatPaymentConfig xylWeChatPaymentConfig;
    @Resource(name = "xinYiLuWeChatPayClient")
    private CloseableHttpClient xinYiLuWeChatPayClient;
    @Resource
    private WeChatPaymentUrlConfig weChatPaymentUrlConfig;
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    @Resource
    private WeChatUtil weChatUtil;
    @Resource
    private NurseAppletChatConfig nurseAppletChatConfig;

    /**
     * 新医路支付商品描述
     */
    private static final String XIN_YI_LU_DESCRIPTION = "山东新医路信息科技有限公司";

    /**
     * JsApi下单成功状态码
     */
    private static final int HAVE_BODY_SUCCESS_CODE = 200;

    /**
     * JsApi下单成功状态码
     */
    private static final int NO_HAVE_BODY_SUCCESS_CODE = 204;

    /**
     * 签名算法
     */
    private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";

    /**
     * 签名方式
     */
    private static final String SIGN_TYPE = "RSA";

    /**
     * 新医路支付回调接口地址
     */
    private static final String XINYILU_WE_CHAT_NOTIFY_URL = "/nurseApp/weChatPayment/xylWeChatPayNotify";

    /**
     * 微信小程序购买商品和健康咨询支付接口
     *
     * @param paymentDTO 输入参数
     * @return 支付结果
     * @throws Exception 常信息
     */
    @Override
    public AjaxResult appletGoodsOrderPay(PaymentDTO paymentDTO) throws Exception {
        String prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo());
        if (StringUtils.isBlank(prepayId)) {
            GoodsOrder goodsOrderInfo = goodsOrderMapper.getGoodsOrderByOrderNo(paymentDTO.getOrderNo());
            if (Objects.isNull(goodsOrderInfo) || StringUtils.isBlank(goodsOrderInfo.getOrderStatus())) {
                return AjaxResult.error("未查询到当前订单信息，请选择正确的订单信息！");
            }
            if (!goodsOrderInfo.getOrderStatus().equals(GooodsOrderStatusEnum.WAIT_PAY.getInfo())) {
                return AjaxResult.error("订单状态异常，无法支付，请联系管理员！");
            }
            if (StringUtils.isBlank(goodsOrderInfo.getBuySource())
                    || !paymentDTO.getBuySource().equals(goodsOrderInfo.getBuySource())) {
                log.info("购买来源不一致，请进行确认！");
                return AjaxResult.error("订单状态异常，无法支付，请联系管理员！");
            }
            String jsApiParams = this.buildGoodsOrderJsApiParams(paymentDTO, goodsOrderInfo);
            prepayId = this.requestJsApiInterface(jsApiParams, paymentDTO, prepayId);
        } else {
            prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo());
        }
        return AjaxResult.success(getSignInfo(prepayId));
    }

    /**
     * 微信小程序预约服务订单支付接口
     *
     * @param paymentDTO 输入参数
     * @return 支付结果
     * @throws Exception 异常信息
     */
    @Override
    public AjaxResult appletAppointmentOrderPay(PaymentDTO paymentDTO) throws Exception {
        String prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo());
        if (StringUtils.isBlank(prepayId)) {
            AppointmentOrder appointmentOrderInfo = appointmentOrderMapper.getAppointmentOrderByOrderNo(paymentDTO.getOrderNo());
            if (Objects.isNull(appointmentOrderInfo) || StringUtils.isBlank(appointmentOrderInfo.getOrderStatus())) {
                return AjaxResult.error("未查询到当前预约订单信息，请选择重新预约！");
            }
            if (!appointmentOrderInfo.getOrderStatus().equals(OrderStatusEnum.WAIT_PAY.getInfo())) {
                return AjaxResult.error("订单状态异常，无法支付，请联系管理员！");
            }
            String jsApiParams = this.buildAppointmentOrderJsApiParams(paymentDTO, appointmentOrderInfo);
            prepayId = this.requestJsApiInterface(jsApiParams, paymentDTO, prepayId);
        } else {
            prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo());
        }
        return AjaxResult.success(getSignInfo(prepayId));
    }

    /**
     * 微信关闭订单方法
     *
     * @param orderNo   订单编号
     * @param buySource 购买来源
     * @return 状态码
     * @throws Exception 异常信息
     */
    @Override
    public int closeWeChatOrderInfo(String orderNo, String buySource) throws Exception {
        String closeUrl = String.format(weChatPaymentUrlConfig.getCloseOrderUrl(), orderNo);
        HttpPost httpPost = new HttpPost(closeUrl);
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("mchid", xylWeChatPaymentConfig.getXylMchId());
        String jsonParams = JSON.toJSONString(paramMap);
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        try (CloseableHttpResponse response = xinYiLuWeChatPayClient.execute(httpPost)) {
            if (response == null) {
                throw new ServiceException("获取微信HttpClient对象失败，请联系管理员！");
            }
            int statusCode = response.getStatusLine().getStatusCode();
            switch (statusCode) {
                case HAVE_BODY_SUCCESS_CODE:
                    log.info("微信关闭订单成功！");
                    break;
                case NO_HAVE_BODY_SUCCESS_CODE:
                    log.info("微信关闭订单成功，无返回体！");
                    break;
                default:
                    throw new ServiceException("微信关闭订单失败！");
            }
            return statusCode;
        }
    }

    /**
     * 查询预约服务订单状态
     *
     * @param orderNo 订单编号
     * @param vo      返回值信息
     */
    @Override
    public OrderStatusInfoVO queryAppointmentOrderStatus(String orderNo, OrderStatusInfoVO vo) {
        String requestUrl = String.format(weChatPaymentUrlConfig.getQueryMchIdUrl(), orderNo);
        requestUrl = requestUrl.concat("?mchid=").concat(xylWeChatPaymentConfig.getXylMchId());
        HttpGet httpGet = new HttpGet(requestUrl);
        httpGet.setHeader("Accept", "application/json");
        try (CloseableHttpResponse response = xinYiLuWeChatPayClient.execute(httpGet)) {
            String responseBody = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HAVE_BODY_SUCCESS_CODE) {
                WeChatQueryOrderVO weChatQueryOrderVO = JSONObject.parseObject(responseBody, WeChatQueryOrderVO.class);
                vo.setPayFlag(StringUtils.isNotBlank(weChatQueryOrderVO.getTradeState())
                        && WeChatTradeStateEnum.NOTPAY.getInfo().equals(weChatQueryOrderVO.getTradeState()));
                vo.setTradeStatus(weChatQueryOrderVO.getTradeState());
                vo.setWeChatQueryOrderVO(weChatQueryOrderVO);
            } else if (statusCode == NO_HAVE_BODY_SUCCESS_CODE) {
                log.warn("请求微信查单接口成功，无返回信息！");
            } else {
                throw new ServiceException("调用微信查询订单接口异常，响应码为：" + statusCode + ", 查询订单返回结果 = " + responseBody);
            }
            return vo;
        } catch (Exception e) {
            log.error("请求微信查单接口异常，异常信息为 ====> {}", e.getMessage());
            throw new ServiceException("查询预约订单状态失败，请联系管理员！");
        }
    }

    /**
     * 查询上商品订单状态
     *
     * @param orderNo 订单编号
     * @param vo      返回值信息
     */
    @Override
    public OrderStatusInfoVO queryGoodsOrderStatus(String orderNo, OrderStatusInfoVO vo, String buySource) {
        String requestUrl = String.format(weChatPaymentUrlConfig.getQueryMchIdUrl(), orderNo).concat("?mchid=").concat(xylWeChatPaymentConfig.getXylMchId());
        HttpGet httpGet = new HttpGet(requestUrl);
        httpGet.setHeader("Accept", "application/json");
        try (CloseableHttpResponse response = xinYiLuWeChatPayClient.execute(httpGet)) {
            if (response == null) {
                throw new ServiceException("获取微信HttpClient对象失败，请联系管理员！");
            }
            String responseBody = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HAVE_BODY_SUCCESS_CODE) {
                WeChatQueryOrderVO weChatQueryOrderVO = JSONObject.parseObject(responseBody, WeChatQueryOrderVO.class);
                vo.setPayFlag(StringUtils.isNotBlank(weChatQueryOrderVO.getTradeState())
                        && WeChatTradeStateEnum.NOTPAY.getInfo().equals(weChatQueryOrderVO.getTradeState()));
                vo.setTradeStatus(weChatQueryOrderVO.getTradeState());
                vo.setWeChatQueryOrderVO(weChatQueryOrderVO);
            }
            if (statusCode == NO_HAVE_BODY_SUCCESS_CODE) {
                log.warn("请求微信查单接口成功，无返回信息！");
            }
            return vo;
        } catch (Exception e) {
            log.error("请求微信查单接口异常，异常信息为 ====> {}", e.getMessage());
            throw new ServiceException("查询商品订单状态失败，请联系管理员！");
        }
    }

    /**
     * 泉医助手小程序-学习培训微信支付
     *
     * @param paymentDTO 支付参数
     * @return 支付结果
     * @throws Exception 异常信息
     */
    @Override
    public AjaxResult appletTrainingOrderPay(PaymentDTO paymentDTO) throws Exception {
        //从Redis中获取当前prepay_id的值
        String prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo());
        if (StringUtils.isBlank(prepayId)) {
            //根据订单编号查询学习培训订单信息
            TrainingOrder trainingOrderInfo = trainingOrderMapper.getTrainingOrderInfoByOrderNo(paymentDTO.getOrderNo());
            if (Objects.isNull(trainingOrderInfo) || StringUtils.isBlank(trainingOrderInfo.getTrainingOrderStatus())) {
                return AjaxResult.error("未查询到当前订单信息，请选择正确的订单信息！");
            }
            //判断订单状态
            if (!StringUtils.equals(trainingOrderInfo.getTrainingOrderStatus(), TrainingOrderStatusEnum.WAIT_PAY.getInfo())) {
                return AjaxResult.error("订单状态异常，无法支付，请联系管理员！");
            }
            if (!StringUtils.equals(paymentDTO.getBuySource(), BuySourceEnum.TRAINING.getInfo())) {
                log.info("购买来源不一致，请进行确认！");
                return AjaxResult.error("订单状态异常，无法支付，请联系管理员！");
            }
            //构建JsApi下单参数
            String jsApiParams = this.buildTrainingOrderJsApiParams(paymentDTO, trainingOrderInfo);
            //请求微信JsApi下单接口获取prepay_id的值
            prepayId = this.requestJsApiInterface(jsApiParams, paymentDTO, prepayId);
        } else {
            prepayId = redisTemplate.opsForValue().get(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo());
        }
        //构建小程序调起支付参数信息
        return AjaxResult.success(getSignInfo(prepayId));
    }

    /**
     * 构建商品订单JsApi微信下单参数
     *
     * @param paymentDTO     前端传值
     * @param goodsOrderInfo 订单信息
     * @return 下单参数json串
     */
    private String buildGoodsOrderJsApiParams(PaymentDTO paymentDTO, GoodsOrder goodsOrderInfo) {
        Map<String, Object> paramMap = new LinkedHashMap<>();
        paramMap.put("mchid", xylWeChatPaymentConfig.getXylMchId());
        paramMap.put("out_trade_no", goodsOrderInfo.getOrderNo());
        paramMap.put("appid", appletChatConfig.getAppletId());
        paramMap.put("description", XIN_YI_LU_DESCRIPTION);
        paramMap.put("notify_url", xylWeChatPaymentConfig.getXylWeChatNotifyUrl() + XINYILU_WE_CHAT_NOTIFY_URL);
        Map<String, Object> amountParamMap = new LinkedHashMap<>();
        int totalPrice = paymentDTO.getPaymentPrice().multiply(BigDecimal.valueOf(100)).intValue();
        amountParamMap.put("total", totalPrice);
        amountParamMap.put("currency", "CNY");
        paramMap.put("amount", amountParamMap);
        Map<String, Object> payerParamMap = new LinkedHashMap<>();
        payerParamMap.put("openid", paymentDTO.getOpenid());
        paramMap.put("payer", payerParamMap);
        return JSON.toJSONString(paramMap);
    }

    /**
     * 请求JsApi下单接口方法
     *
     * @param jsApiParams 下单参数
     * @param paymentDTO  输入参数
     * @param prepayId    JsAp下单接口返回值
     * @return 结果
     */
    private String requestJsApiInterface(String jsApiParams, PaymentDTO paymentDTO, String prepayId) {
        StringEntity stringEntity = new StringEntity(jsApiParams, StandardCharsets.UTF_8);
        stringEntity.setContentType("application/json");
        HttpPost httpPost = new HttpPost(weChatPaymentUrlConfig.getJsapiPalceOrderUrl());
        httpPost.setEntity(stringEntity);
        httpPost.setHeader("Accept", "application/json");
        try (CloseableHttpResponse response = xinYiLuWeChatPayClient.execute(httpPost)) {
            if (Objects.isNull(response)) {
                log.error("JsApi下单接口执行错误， 执行账户为 ====> 山东新医路信息科技有限公司");
                throw new ServiceException("JsApi下单接口执行异常，请联系管理员！");
            }
            int statusCode = response.getStatusLine().getStatusCode();
            String result = EntityUtils.toString(response.getEntity());
            if (statusCode == HAVE_BODY_SUCCESS_CODE) {
                Map<String, Object> resultMap = JSONObject.parseObject(result);
                prepayId = resultMap.getOrDefault("prepay_id", "").toString();
                redisTemplate.opsForValue().set(Constants.PREPAY_ID_KEY + paymentDTO.getOrderNo(), prepayId, 5400, TimeUnit.SECONDS);
            } else if (statusCode == NO_HAVE_BODY_SUCCESS_CODE) {
                log.info("JsApi下单成功，无返回值信息！");
            } else {
                log.error("JsApi下单失败，失败原因为 ====> {}", result);
                throw new ServiceException("JsApi下单失败，失败原因为：" + result);
            }
        } catch (Exception e) {
            log.error("JsApi下单失败，失败原因 =====> {}", e.getMessage());
            throw new ServiceException("JsApi下单失败，请联系管理员！");
        }
        return prepayId;
    }

    /**
     * 构建微信小程序调起支付参数设置
     *
     * @param prepayId jsapi下单返回参数信息
     * @return 参数信息
     * @throws Exception 异常信息
     */
    private WeChatAppletSignVO getSignInfo(String prepayId) throws Exception {
        String appId = appletChatConfig.getAppletId();
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        PrivateKey privateKey = weChatUtil.getPrivateKey(xylWeChatPaymentConfig.getXylPrivateKeyPath());
        if (Objects.isNull(privateKey)) {
            throw new ServiceException("获取商户私钥失败，请联系管理员！");
        }
        prepayId = "prepay_id=" + prepayId;
        String signatureStr = Stream.of(appId, timestamp, nonceStr, prepayId)
                .collect(Collectors.joining("\n", "", "\n"));
        log.info("计算签名认证信息 =====> {}", signatureStr);
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);
        sign.initSign(privateKey);
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        String paySign = Base64Utils.encodeToString(sign.sign());
        return WeChatAppletSignVO.builder().appId(appId).timeStamp(timestamp).nonceStr(nonceStr).prepayId(prepayId)
                .signType(SIGN_TYPE).paySign(paySign).build();
    }

    /**
     * 构建预约订单JsApi微信下单参数
     *
     * @param paymentDTO       前端传值
     * @param appointmentOrder 预约订单信息
     * @return 下单参数json串
     */
    private String buildAppointmentOrderJsApiParams(PaymentDTO paymentDTO, AppointmentOrder appointmentOrder) {
        Map<String, Object> paramMap = new LinkedHashMap<>();
        paramMap.put("mchid", xylWeChatPaymentConfig.getXylMchId());
        paramMap.put("out_trade_no", appointmentOrder.getOrderNo());
        paramMap.put("appid", appletChatConfig.getAppletId());
        paramMap.put("description", XIN_YI_LU_DESCRIPTION);
        paramMap.put("notify_url", xylWeChatPaymentConfig.getXylWeChatNotifyUrl() + XINYILU_WE_CHAT_NOTIFY_URL);
        Map<String, Object> amountParamMap = new LinkedHashMap<>();
        int totalPrice = appointmentOrder.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue();
        amountParamMap.put("total", totalPrice);
        amountParamMap.put("currency", "CNY");
        paramMap.put("amount", amountParamMap);
        Map<String, Object> payerParamMap = new LinkedHashMap<>();
        payerParamMap.put("openid", paymentDTO.getOpenid());
        paramMap.put("payer", payerParamMap);
        return JSON.toJSONString(paramMap);
    }


    /**
     * 构建学习培训订单JsApi微信下单参数
     *
     * @param paymentDTO    前端传值
     * @param trainingOrder 学习培训订单信息
     * @return 下单参数json串
     */
    private String buildTrainingOrderJsApiParams(PaymentDTO paymentDTO, TrainingOrder trainingOrder) {
        Map<String, Object> paramMap = new LinkedHashMap<>();
        //医护助手学习培训订单使用医路优品账户进行支付，无需根据进购买来源进行判断
        paramMap.put("mchid", xylWeChatPaymentConfig.getXylMchId());
        paramMap.put("out_trade_no", trainingOrder.getTrainingOrderNo());
        //应用id，此处为医护助手小程序id
        paramMap.put("appid", nurseAppletChatConfig.getAppletId());
        paramMap.put("description", XIN_YI_LU_DESCRIPTION);
        paramMap.put("notify_url", xylWeChatPaymentConfig.getXylWeChatNotifyUrl() + XINYILU_WE_CHAT_NOTIFY_URL);
        Map<String, Object> amountParamMap = new LinkedHashMap<>();
        //总金额
        int totalPrice = paymentDTO.getPaymentPrice().multiply(BigDecimal.valueOf(100)).intValue();
        amountParamMap.put("total", totalPrice);
        //货币类型
        amountParamMap.put("currency", "CNY");
        paramMap.put("amount", amountParamMap);
        //支付者信息
        Map<String, Object> payerParamMap = new LinkedHashMap<>();
        payerParamMap.put("openid", paymentDTO.getOpenid());
        paramMap.put("payer", payerParamMap);
        return JSON.toJSONString(paramMap);
    }
}
