package com.ruoyi.web.service;

import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.enums.BindStatus;
import com.ruoyi.common.enums.ECardPayStatus;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.QRCodeUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.dz.domain.DzCards;
import com.ruoyi.dz.service.IDzCardsService;
import com.ruoyi.enums.CardDistributeStatus;
import com.ruoyi.enums.CardStatus;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.IZuserTokenService;
import com.ruoyi.voluntary.domain.BBusiPaymentOrders;
import com.ruoyi.voluntary.service.IBBusiPaymentOrdersService;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.util.Date;

@Service
@Slf4j
public class WeixinPayService {
    CloseableHttpClient httpClient;
    @Value("${wxpay.appId}")
    private String appId;
    @Value("${wxpay.miniAppId}")
    private String miniAppId;
    private String mchId;
    private String notifyUrl;
    private AesUtil aesUtil;
    private ObjectMapper om = new ObjectMapper();
    @Autowired
    private IBBusiPaymentOrdersService paymentOrdersService;
    @Autowired
    private IDzCardsService cardsService;
    private final ISysUserService sysUserService;
    private final SysLoginService sysLoginService;
    private final ISysConfigService sysConfigService;


    public WeixinPayService(@Value("${wxpay.mchid}") String mchId, @Value("${wxpay.key}") String apiV3Key,
                            @Value("${wxpay.mchsn}") String mchSerialNo, @Value("${wxpay.privateKey}") String privateKey, ISysUserService sysUserService
                            , SysLoginService sysLoginService, ISysConfigService sysConfigService) throws Exception {
        this.mchId = mchId;
        this.sysUserService = sysUserService;
        this.sysLoginService = sysLoginService;
        this.sysConfigService = sysConfigService;
        // 加载商户私钥（privateKey：私钥字符串）
        // String privateKey = getKey();
        // String mchSerialNo = "21925f369f23cdf8ea3a913c541d4239d1bcc8af";
        // String apiV3Key = "zhilongsanjiasanzhilongsanjiasan";
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
        byte[] apiV3KeyBytes = apiV3Key.getBytes("utf-8");
        // 加载平台证书（mchId：商户号,mchSerialNo：商户证书序列号,apiV3Key：V3密钥）
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3KeyBytes);
        aesUtil = new AesUtil(apiV3KeyBytes);
        // 初始化httpClient
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier)).build();
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        String urlPrefix = sysConfigService.selectConfigByKey("pay.callback.url");
        notifyUrl = urlPrefix + "/front/ecard/payResult";

//        Date timeExpire = DateUtils.addMonths(new Date(), 1);
//        PayOrderReq req = new PayOrderReq(timeExpire, 1, mchId, "description", "notifyUrl", "orderId", "appId");
//        String reqData = om.writeValueAsString(req);

//        String url = "https://online.fliphtml5.com/jkrou/kfob/";
//        ByteArrayOutputStream bos = new ByteArrayOutputStream();
//        try {
//            Image src = ImageIO.read(WeixinPayService.class.getClassLoader().getResourceAsStream("mingxue60.jpg"));
//            QRCodeUtils.encode(url, src, bos);
//            String img = "data:image/jpeg;base64," + Base64.encodeBase64String(bos.toByteArray());
//            System.out.println(img);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
        return;
    }

    public String getAppId(TradeTypeEnum tradeType) {
        return TradeTypeEnum.JSAPI.equals(tradeType) ? miniAppId : appId;
    }

    public void checkWxPay() {
        BBusiPaymentOrders cond = new BBusiPaymentOrders();
        cond.setStatus(ECardPayStatus.unpaid.getValue());
        Date limit = DateUtils.addDays(new Date(), -2);
        for (BBusiPaymentOrders order : paymentOrdersService.selectBBusiPaymentOrdersList(cond)) {
            if (order.getCreateTime().before(limit)) { // 忽略两天前的
                continue;
            }
            String outTradeNo = order.getPhonenumber() + "_" + order.getId();
            try {
                PayOrderReq payResult = queryOrder(outTradeNo);
                if (null == payResult.getCode()) {
                    processPaySuccess(payResult);
                } else if ("404".equals(payResult.getCode())) {
                    continue;
                } else {
                    log.warn("check Pay code: {}, text: {}", payResult.getCode(), payResult.getText());
                }
            } catch (Exception e) {
                log.warn("check Pay ", e);
                continue;
            }
        }
    }

    public void processWxPayFail(Long orderId, String payer, String feedback) {
        BBusiPaymentOrders order = paymentOrdersService.selectBBusiPaymentOrdersById(orderId);
        if (null == order || ECardPayStatus.unpaid.getValue() != order.getStatus()) {
            log.warn("订单状态更新失败{}", orderId);
            return;
        }
        BBusiPaymentOrders upOrder = new BBusiPaymentOrders();
        upOrder.setId(order.getId());
        upOrder.setStatus(ECardPayStatus.payFailed.getValue());
        upOrder.setFeedBack(feedback);
        upOrder.setPayer(payer);
        upOrder.setPayTime(new Date());
        paymentOrdersService.updateBBusiPaymentOrders(upOrder);
    }

    public void processPaySuccess(Long orderId, String transactionId, String tradeState, String payer, String feedback) {
        BBusiPaymentOrders order = paymentOrdersService.selectBBusiPaymentOrdersById(orderId);
        if (null == order || ECardPayStatus.unpaid.getValue() != order.getStatus()) {
            log.warn("WxPay 订单不存在或非未支付状态 {}", orderId);
            return;
        }
        if (!"SUCCESS".equals(tradeState)) {
            BBusiPaymentOrders upOrder = new BBusiPaymentOrders();
            upOrder.setId(order.getId());
            upOrder.setStatus(ECardPayStatus.payFailed.getValue());
            upOrder.setFeedBack(feedback);
            upOrder.setPayer(payer);
            upOrder.setPayTime(new Date());
            upOrder.setTransactionId(transactionId);
            upOrder.setStatus(ECardPayStatus.payFailed.getValue());
            paymentOrdersService.updateBBusiPaymentOrders(upOrder);
            log.warn("WxPay 订单支付失败 {}", orderId);
            return;
        }
        //查找一张未使用的电子卡
        DzCards eCard = cardsService.selectOneECard();
        if(null == eCard) {
            log.warn("WxPay 无有效电子卡 {}", orderId);
            return;
        }
        //修改电子卡已使用的状态。 电子卡新增时需要直接分配代理商，学生注册时year与outTime需要取b_busi_payment_orders里面的值（20220901已完成）
        eCard.setStatus(CardStatus.Free.getVal());
        eCard.setDistributeStatus(CardDistributeStatus.Assign.getVal());
        eCard.setDistributeTime(DateUtils.getNowDate());
        cardsService.updateDzCards(eCard);

        order.setCardId(eCard.getCardId());
        order.setCardNo(eCard.getCardNo());

        order.setTransactionId(transactionId);
        order.setStatus(ECardPayStatus.paid.getValue());
        paymentOrdersService.updateBBusiPaymentOrders(order);

        Long userId = Long.parseLong(order.getCustomerCode());
        SysUser upUser = new SysUser();
        upUser.setUserId(userId);
        upUser.setBindStatus(BindStatus.Pend.code());
        upUser.setCardId(eCard.getCardId());
        sysUserService.updateUserProfile(upUser);
        sysLoginService.updateBindStatus(upUser.getUserId(), upUser.getBindStatus());
    }

    public void processPaySuccess(PayOrderReq payResult) {
        if(!payResult.getOutTradeNo().startsWith("test")) {
            //15111096866_59  phonenumber_orderId
            Long orderId = Convert.toLongArray("_", payResult.getOutTradeNo())[1];
            PayOrderPayer payer = payResult.getPayer();
            processPaySuccess(orderId, payResult.getTransactionId(), payResult.getTradeState(), null != payer ? payer.openid : "", payResult.getText());
        }
    }

    public void processWxPayResult(String serial, String signatureType, String timestamp, String nonce, String signature, String json) throws Exception {
        log.info("WxPay {}-{}-{}-{}-{} recv {}", serial, signatureType, timestamp, nonce, signature, json);
        PayCallback payCallback = om.readValue(json, PayCallback.class);
        PayCallbackResource resource = payCallback.getResource();

        String result = aesUtil.decryptToString(resource.getAssociatedData().getBytes(), resource.getNonce().getBytes(), resource.getCiphertext());
        PayOrderReq payResult = om.readValue(result, PayOrderReq.class);
        payResult.setText(result);
        log.info("WxPay {} proc {}", serial, result);
        processPaySuccess(payResult);
        return;
    }

    public String encodeQrCode(String url) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            InputStream is = new ClassPathResource("/mingxue60.jpg").getInputStream();
            QRCodeUtils.encode(url, null != is ? ImageIO.read(is) : null, bos);
            return "data:image/jpeg;base64," + Base64.encodeBase64String(bos.toByteArray());
        } catch (Exception e) {
            log.error("encodeQrCode", e);
        }
        return url;
    }

    public PayOrderReq queryOrder(String outTradeNo) throws Exception {
        URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo);
        uriBuilder.setParameter("mchid", mchId);

        //完成签名并执行请求
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = httpClient.execute(httpGet);

        try {
            int statusCode = response.getStatusLine().getStatusCode();
            String result = EntityUtils.toString(response.getEntity());
            if (statusCode == 200) {
                PayOrderReq payResult = om.readValue(result, PayOrderReq.class);
                payResult.setText(result);
                return payResult;
            } else if (StringUtils.isNotBlank(result)) {
                PayOrderReq payResult = new PayOrderReq();
                payResult.setCode(String.valueOf(statusCode));
                payResult.setText(result);
                return payResult;
            } else {
                throw new IOException("failed,code = " + statusCode + ",body = " + result);
            }
        } finally {
            response.close();
        }
    }

    public String createOrderPayUrl(String orderId, Integer total, String description, PayOrderSceneInfo sceneInfo) throws Exception{
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/" + (null != sceneInfo ? "h5" : "native"));
        Date timeExpire = DateUtils.addMonths(new Date(), 1);
        PayOrderReq req = new PayOrderReq(timeExpire, total, mchId, description, notifyUrl, orderId, appId);
        req.setSceneInfo(sceneInfo);
        log.info("PayOrderReq is {}", JSONObject.toJSONString(req));
        String reqData = om.writeValueAsString(req);
        StringEntity entity = new StringEntity(reqData,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) { //处理成功
                CreateOrderResp resp = om.readValue(EntityUtils.toString(response.getEntity()), CreateOrderResp.class);
                return null != sceneInfo ? resp.h5Url : resp.codeUrl;
            } else {
                throw new IOException("WxFail, code = " + statusCode + ",body = " + EntityUtils.toString(response.getEntity()));
            }
        } finally {
            response.close();
        }
    }

    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @AllArgsConstructor
    @NoArgsConstructor
    public static class PayOrderSceneH5Info {
        String type;
    }

    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @AllArgsConstructor
    @NoArgsConstructor
    public static class PayOrderSceneInfo {
        @JsonProperty("payer_client_ip")
        String payerClientIp;
        @JsonProperty("h5_info")
        PayOrderSceneH5Info h5Info;

        public PayOrderSceneInfo(String ip, String os) {
            payerClientIp = ip;
            h5Info = new PayOrderSceneH5Info(os);
        }
    }

    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class PayOrderAmount {
        Integer total;
        String currency = "CNY";
        @JsonProperty("payer_total")
        Integer payTotal;
        @JsonProperty("payer_currency")
        String payCurrency;
    }
    @Data
    public static class PayOrderPayer {
        String openid;
    }
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @NoArgsConstructor
    public static class PayOrderReq {
        @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone="GMT+8")
        @JsonProperty("time_expire")
        Date timeExpire;
        PayOrderAmount amount;
        @JsonProperty("scene_info")
        PayOrderSceneInfo sceneInfo;
        String mchid;
        String description;
        @JsonProperty("notify_url")
        String notifyUrl;
        @JsonProperty("out_trade_no")
        String outTradeNo;
        String appid;

        PayOrderPayer payer;
        @JsonProperty("transaction_id")
        String transactionId;
        @JsonProperty("trade_type")
        String tradeType;
        @JsonProperty("trade_state")
        String tradeState;
        @JsonProperty("trade_state_desc")
        String tradeStateDesc;
        @JsonProperty("bank_type")
        String bankType;
        String attach;
        @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone="GMT+8")
        @JsonProperty("success_time")
        Date successTime;
        @JsonIgnore
        String text;
        @JsonIgnore
        String code;

        public PayOrderReq(Date timeExpire, Integer total, String mchid, String description, String notifyUrl, String outTradeNo, String appid) {
            this.timeExpire = timeExpire;
            amount = new PayOrderAmount();
            amount.setTotal(total);
            this.mchid = mchid;
            this.description = description;
            this.notifyUrl = notifyUrl;
            this.outTradeNo = outTradeNo;
            this.appid = appid;
        }
    }

    @Data
    public static class CreateOrderResp {
        @JsonProperty("code_url")
        String codeUrl;
        @JsonProperty("h5_url")
        String h5Url;
    }

    @Data
    public static class PayCallbackResource {
        @JsonProperty("original_type")
        String originalType;
        String algorithm;
        String ciphertext;
        @JsonProperty("associated_data")
        String associatedData;
        String nonce;
    }

    @Data
    public static class PayCallback {
        String id;
        @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone="GMT+8")
        @JsonProperty("create_time")
        Date createTime;
        @JsonProperty("resource_type")
        String resourceType;
        @JsonProperty("event_type")
        String eventType;
        String summary;
        PayCallbackResource resource;
    }
}
