package com.zanxiang.game.module.sdk.service.pay;

import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.app.models.AlipayTradeAppPayResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeQueryResponse;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.alipay.easysdk.payment.wap.models.AlipayTradeWapPayResponse;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.zanxiang.game.module.base.pojo.enums.HttpStatusEnum;
import com.zanxiang.game.module.base.pojo.enums.OsEnum;
import com.zanxiang.game.module.mybatis.entity.Order;
import com.zanxiang.game.module.sdk.enums.OrderStateEnum;
import com.zanxiang.game.module.sdk.pojo.dto.*;
import com.zanxiang.game.module.sdk.service.IPayApplicationService;
import com.zanxiang.game.module.sdk.service.IPayMerchantService;
import com.zanxiang.game.module.sdk.util.HttpUtil;
import com.zanxiang.module.util.DateUtil;
import com.zanxiang.module.util.JsonUtil;
import com.zanxiang.module.util.URIUtil;
import com.zanxiang.module.util.exception.BaseException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.net.URL;
import java.time.LocalDateTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author : lingfeng
 * @time : 2022-09-28
 * @description : 支付宝支付
 */
@Slf4j
@Service
public class AliPayService extends PayBaseService {

    private Pattern pattern = Pattern.compile("action=\"(.*)\">", Pattern.CASE_INSENSITIVE);

    private Pattern param = Pattern.compile("value=\"(.*)\">", Pattern.CASE_INSENSITIVE);

    /**
     * 支付成功
     */
    private static final String TRADE_STATUS_SUCCESS = "TRADE_SUCCESS";

    /**
     * 订单关闭
     */
    private static final String TRADE_STATUS_CLOSED = "TRADE_CLOSED";

    /**
     * UTF-8 字符集
     */
    private static final String UTF8 = "UTF-8";

    /**
     * 订单超时自动关闭时间
     */
    private static final long TIME_EXPIRE = 5L;

    /**
     * 同步回调地址配置
     */
    @Value("${payConfig.aliPay.notifyUrl}")
    private String notifyUrlConfig;

    @Autowired
    private IPayMerchantService payMerchantService;

    @Autowired
    private IPayApplicationService payApplicationService;

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public Map<String, Object> create(ProductPayParamDTO product, GamePayWayDTO gamePayWayDTO) {
        //初始化支付配置
        this.configInit(gamePayWayDTO);
        //回调地址中拼接游戏具体支付方式
        String notifyUrl = this.notifyUrlConfig + "?" + "gamePayWayId=" + gamePayWayDTO.getId();
        //支付方式
        int payDevice = product.getPayDevice().intValue();
        //不同的支付途径
        Map<String, Object> resultMap;
        switch (payDevice) {
            case 1:
                resultMap = this.pcPay(product, notifyUrl);
                break;
            case 2:
                resultMap = this.h5Pay(product, notifyUrl);
                break;
            case 3:
                resultMap = this.appPay(product, notifyUrl);
                break;
            default:
                throw new RuntimeException("未知支付方式");
        }
        //订单支付添加过期缓存
        this.orderExpire(product.getOutTradeNo());
        //商户信息
        PayMerchantDTO payMerchantDTO = payMerchantService.getByMerchantNo(gamePayWayDTO.getMerchantNo());
        //更新订单商户信息
        orderService.update(new LambdaUpdateWrapper<Order>()
                .set(Order::getMerchantNo, payMerchantDTO.getMerchantNo())
                .eq(Order::getOrderId, product.getOutTradeNo()));
        log.error("返回支付参数 resultMap : {}", JsonUtil.toString(resultMap));
        //返回结果
        return resultMap;
    }

    @Override
    public String notify(HttpServletRequest request, HttpServletResponse response) {
        //解密回调参数
        Map<String, String> params = new HashMap<>(26);
        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String parameterName = parameterNames.nextElement();
            params.put(parameterName, request.getParameter(parameterName));
        }
        //订单号orderId
        String orderNo = request.getParameter("out_trade_no");
        //订单支付结果
        String tradeStatus = params.get("trade_status");
        //订单支付方式id, 因为回调地址拼接参数不参加sign验证, 所以这里使用移除
        String gamePayWayId = params.remove("gamePayWayId");
        log.error("支付宝异步回调获取到的参数, gamePayWayId : {},  params : {}", gamePayWayId, JsonUtil.toString(params));
        //初始化配置
        this.configInit(gamePayWayService.getById(Long.valueOf(gamePayWayId)));
        //支付宝签名认证
        Boolean verifyNotify;
        try {
            verifyNotify = Factory.Payment.Common().verifyNotify(params);
        } catch (Exception e) {
            log.error("支付宝异步回调-验证签名异常 params : {}, e : {}", JsonUtil.toString(params), e.getMessage());
            return HttpStatusEnum.FAIL.getMsg();
        }
        if (!verifyNotify) {
            log.error("支付宝异步回调-验证签名失败, params : {}", JsonUtil.toString(params));
            return HttpStatusEnum.FAIL.getMsg();
        }
        //签名验证通过, 判断是否支付成功
        if (Objects.equals(TRADE_STATUS_SUCCESS, tradeStatus)) {
            this.paySuccess(orderNo, params.get("total_amount"), params.get("trade_no"));
        }
        //订单自动关闭
        if (Objects.equals(TRADE_STATUS_CLOSED, tradeStatus)) {
            orderService.update(new LambdaUpdateWrapper<Order>()
                    .set(Order::getStatus, OrderStateEnum.CANCEL_PAY.getCode())
                    .set(Order::getUpdateTime, LocalDateTime.now())
                    .eq(Order::getOrderId, orderNo));
        }
        //返回消息接收成功
        return HttpStatusEnum.SUCCESS.getMsg();
    }

    @Override
    public void closeOrder(PlatformOrderDTO platformOrderDTO) {
        //支付配置
        GamePayWayDTO gamePayWayDTO = gamePayWayService.closeOrderGetGamePayWay(platformOrderDTO.getGameId(),
                platformOrderDTO.getPayWayId(), platformOrderDTO.getPayDeviceId());
        //初始化配置
        this.configInit(gamePayWayDTO);
        //订单id
        String orderId = platformOrderDTO.getOrderId();
        //查询订单
        AlipayTradeQueryResponse queryResponse;
        try {
            queryResponse = Factory.Payment.Common().query(orderId);
        } catch (Exception e) {
            log.error("关闭订单查询订单信息异常, orderId : {}, e : {}", orderId, e.getMessage());
            return;
        }
        //未下单的订单, 直接取消(后续如果用户成功支付, 也是可以收到支付成功的回调的)
        if (!ResponseChecker.success(queryResponse)) {
            log.error("关闭订单查询订单信息不存在, orderId : {}, queryResponse : {}", orderId, JsonUtil.toString(queryResponse));
            orderService.update(new LambdaUpdateWrapper<Order>()
                    .set(Order::getStatus, OrderStateEnum.CANCEL_PAY.getCode())
                    .set(Order::getUpdateTime, LocalDateTime.now())
                    .eq(Order::getOrderId, orderId));
        }
    }

    private Map<String, Object> h5Pay(ProductPayParamDTO product, String notifyUrl) {
        AlipayTradeWapPayResponse response;
        try {
            response = Factory.Payment.Wap().asyncNotify(notifyUrl)
                    .optional("time_expire", DateUtil.formatLocalDateTime(LocalDateTime.now().plusMinutes(TIME_EXPIRE)))
                    .pay(product.getSubject(), product.getOutTradeNo(), product.getTotalFee(), "", "");
        } catch (Exception e) {
            log.error("支付宝H5支付异常, product : {}, e:{}", JsonUtil.toString(product), e.getMessage());
            this.listenCall(product.getGameId(), "阿宝网页端支付异常");
            throw new BaseException("支付宝H5支付异常");
        }
        //请求失败
        if (!ResponseChecker.success(response)) {
            log.error("支付宝H5支付失败, product : {}, response : {}", JsonUtil.toString(product), JsonUtil.toString(response));
            throw new BaseException("支付宝H5支付失败");
        }
        //结果数据
        String body = response.getBody();
        Map<String, Object> payParamMap = new HashMap<>(3);
        payParamMap.put("deviceType", product.getPayDevice());
        payParamMap.put("orderId", product.getOutTradeNo());
        //非手机系统, 不走deepLink
        if (!product.getDeviceSystem().contains(OsEnum.SYSTEM_ANDROID.getOs())
                && !product.getDeviceSystem().contains(OsEnum.SYSTEM_OHOS.getOs())
                && !product.getDeviceSystem().contains(OsEnum.SYSTEM_IOS.getOs())
        ) {
            payParamMap.put("data", body);
            return payParamMap;
        }
        //解析短链deepLink
        String deepLink = this.getH5DeepLink(body, product.getDeviceSystem());
        payParamMap.put("data", deepLink);
        return payParamMap;
    }

    private String getH5DeepLink(String body, String deviceSystem) {
        Matcher matcher = pattern.matcher(body);
        if (!matcher.find()) {
            log.error("deepLink短链pattern正则匹配结果为空, body : {}", body);
            throw new BaseException("deepLink短链pattern正则匹配结果为空");
        }
        Matcher paraMatcher = param.matcher(body);
        if (!paraMatcher.find()) {
            log.error("deepLink短链param正则匹配结果为空, body : {}", body);
            throw new BaseException("deepLink短链param正则匹配结果为空");
        }
        String url = matcher.group();
        url = url.substring(8, url.length() - 2);
        String paramData = paraMatcher.group();
        paramData = paramData.substring(7, paramData.length() - 2);
        paramData = paramData.replace("&quot;", "\"");
        String mobileClientUrl = HttpUtil.postDataUrl(url, "biz_content=" + paramData, null);
        if (Strings.isBlank(mobileClientUrl)) {
            log.error("deepLink短链接口调用返回为空, mobileClientUrl : {}", mobileClientUrl);
            throw new BaseException("deepLink短链接口调用返回为空");
        }
        String deepLinkAndroid = "alipays://platformapi/startApp?appId=20000125&orderSuffix=";
        String deepLinkIos = "alipay://alipayclient/?";
        Map<String, String> deepLinkParamMap = this.deepLinkParamMap(mobileClientUrl);
        deepLinkAndroid += URIUtil.encodeURIComponent(deepLinkParamMap.get("dataString"), UTF8);
        deepLinkIos += URIUtil.encodeURIComponent(JsonUtil.toString(deepLinkParamMap), UTF8);
        return Objects.equals(deviceSystem, OsEnum.SYSTEM_ANDROID.getOs())
                || Objects.equals(deviceSystem, OsEnum.SYSTEM_OHOS.getOs()) ? deepLinkAndroid : deepLinkIos;
    }

    private Map<String, String> deepLinkParamMap(String mobileClientUrl) {
        String[] paramArray;
        try {
            URL mobileUrl = new URL(mobileClientUrl);
            String query = mobileUrl.getQuery();
            paramArray = query.split("&");
        } catch (Exception e) {
            log.error("deepLink短链encode异常, e : {}", e.getMessage());
            throw new BaseException("deepLink短链encode异常");
        }
        Map<String, Object> paramMap = new LinkedHashMap<>();
        if (paramArray.length > 0) {
            for (String param : paramArray) {
                int idx = param.indexOf("=");
                paramMap.put(param.substring(0, idx), param.substring(idx + 1));
            }
        }
        paramMap.put("t", System.currentTimeMillis());
        String url = "https://mclient.alipay.com/wapcashier/api/cashierMain.json";
        HttpHeaders requestHeader = new HttpHeaders();
        requestHeader.put(HttpHeaders.REFERER, Collections.singletonList("https://mclient.alipay.com"));
        requestHeader.put(HttpHeaders.CONTENT_TYPE, Collections.singletonList("application/json"));
        ResponseEntity<String> res;
        try {
            res = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(paramMap, requestHeader), String.class);
        } catch (Exception e) {
            log.error("deepLink短链接口调用异常, mobileClientUrl : {}, e : {}", mobileClientUrl, e.getMessage());
            throw new BaseException("deepLink短链接口调用异常");
        }
        String body = res.getBody();
        if (Strings.isBlank(body)) {
            log.error("deepLink短链接口调用返回结果为空, mobileClientUrl : {}", mobileClientUrl);
            throw new BaseException("deepLink短链接口调用返回结果为空");
        }
        AliPayLinkDTO aliPayLinkDTO = JsonUtil.toObj(body, AliPayLinkDTO.class);
        if (!aliPayLinkDTO.isSuccess()) {
            log.error("deepLink短链接口调用返回结果失败, mobileClientUrl : {}", mobileClientUrl);
            throw new BaseException("deepLink短链接口调用返回结果失败");
        }
        return JsonUtil.toMap(JsonUtil.toString(aliPayLinkDTO.getData().getBizData().getInvokeAlipayData()), Map.class, String.class);
    }

    private Map<String, Object> pcPay(ProductPayParamDTO product, String notifyUrl) {
        AlipayTradePagePayResponse response;
        try {
            response = Factory.Payment.Page().asyncNotify(notifyUrl)
                    .optional("time_expire", DateUtil.formatLocalDateTime(LocalDateTime.now().plusMinutes(TIME_EXPIRE)))
                    .pay(product.getSubject(), product.getOutTradeNo(), product.getTotalFee(), "");
        } catch (Exception e) {
            log.error("支付宝pc端支付异常, product : {}, e:{}", JsonUtil.toString(product), e.getMessage());
            this.listenCall(product.getGameId(), "阿宝电脑端支付异常");
            throw new BaseException("支付宝pc端支付异常");
        }
        if (!ResponseChecker.success(response)) {
            log.error("支付宝pc端支付失败, product : {}, response : {}", JsonUtil.toString(product), JsonUtil.toString(response));
            throw new BaseException("支付宝pc端支付失败");
        }
        Map<String, Object> payParamMap = new HashMap<>(3);
        payParamMap.put("deviceType", product.getPayDevice());
        payParamMap.put("data", response.getBody());
        payParamMap.put("orderId", product.getOutTradeNo());
        return payParamMap;
    }

    private Map<String, Object> appPay(ProductPayParamDTO product, String notifyUrl) {
        AlipayTradeAppPayResponse response;
        try {
            response = Factory.Payment.App().asyncNotify(notifyUrl)
                    .optional("time_expire", DateUtil.formatLocalDateTime(LocalDateTime.now().plusMinutes(TIME_EXPIRE)))
                    .pay(product.getSubject(), product.getOutTradeNo(), product.getTotalFee());
        } catch (Exception e) {
            log.error("支付宝app端支付异常, product : {}, e:{}", JsonUtil.toString(product), e.getMessage());
            this.listenCall(product.getGameId(), "阿宝移动端支付异常");
            throw new BaseException("支付宝app端支付异常");
        }
        if (!ResponseChecker.success(response)) {
            log.error("支付宝app端支付失败, product : {}, response : {}", JsonUtil.toString(product), JsonUtil.toString(response));
            throw new BaseException("支付宝app端支付失败");
        }
        Map<String, Object> payParamMap = new HashMap<>(3);
        payParamMap.put("deviceType", product.getPayDevice());
        payParamMap.put("data", response.getBody());
        payParamMap.put("orderId", product.getOutTradeNo());
        return payParamMap;
    }

    private void configInit(GamePayWayDTO gamePayWayDTO) {
        //商户信息
        PayMerchantDTO payMerchantDTO = payMerchantService.getByMerchantNo(gamePayWayDTO.getMerchantNo());
        //查询支付应用信息
        PayApplicationDTO payApplicationDTO = payApplicationService.getPayApplicationByAppId(gamePayWayDTO.getAppId());
        //支付参数
        Map<String, String> payConfigMap = JsonUtil.toMap(payMerchantDTO.getPayConfig(), Map.class, String.class);
        //支付宝应用id和应用私钥
        payConfigMap.put("appId", payApplicationDTO.getAppId());
        payConfigMap.put("merchantPrivateKey", payApplicationDTO.getAppSecret());
        //赋值配置信息
        Config config = JsonUtil.toObj(JsonUtil.toString(payConfigMap), Config.class);
        Factory.setOptions(config);
    }
}