package com.izouma.nineth.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.github.kevinsawicki.http.HttpRequest;
import com.izouma.nineth.config.Constants;
import com.izouma.nineth.config.GeneralProperties;
import com.izouma.nineth.config.HmPayProperties;
import com.izouma.nineth.config.RedisKeys;
import com.izouma.nineth.dto.PayQuery;
import com.izouma.nineth.enums.PayStatus;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.utils.DateTimeUtils;
import com.izouma.nineth.utils.SnowflakeIdWorker;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
@AllArgsConstructor
@EnableConfigurationProperties({HmPayProperties.class})
public class HMPayService {
    private final HmPayProperties               hmPayProperties;
    private final SnowflakeIdWorker             snowflakeIdWorker;
    private final RedisTemplate<String, Object> redisTemplate;

    public String paddingOrderId(String orderId) {
        if (orderId != null && orderId.length() < 12) {
            StringBuilder orderIdBuilder = new StringBuilder(orderId);
            for (int i = orderIdBuilder.length(); i < 12; i++) {
                orderIdBuilder.insert(0, "0");
            }
            orderId = orderIdBuilder.toString();
        }
        return orderId;
    }

    private String getSign(Map<String, String> params) {
        try {
            Map<String, String> m = new HashMap<>(params);
            m.remove("expire_time");
            m.remove("goods_name");
            m.remove("product_code");
            m.remove("clear_cycle");
            m.remove("jump_scheme");
            m.remove("meta_option");
            return AlipaySignature.rsaSign(m, hmPayProperties.getAppPrivateKey(), "UTF-8");
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void encodeParams(Map<String, String> params) {
        List<String> arr = Arrays.asList("notify_url", "return_url", "goods_name", "meta_option", "sign", "pay_extra");
        params.forEach((k, v) -> {
            if (arr.contains(k)) {
                params.put(k, URLEncoder.encode(v, StandardCharsets.UTF_8));
            }
        });
    }

    public JSONObject requestApi(String method, Map<String, String> bizContent) {
        Map<String, String> params = new HashMap<>();
        params.put("app_id", hmPayProperties.getMerNo());
        params.put("method", method);
        params.put("timestamp", DateTimeUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));
        params.put("nonce", RandomStringUtils.randomAlphabetic(32));

        params.put("biz_content", JSON.toJSONString(bizContent));
        params.put("sign", getSign(params));
        log.info("请求参数{}", params);
        String body = HttpRequest.post("https://hmpay.sandpay.com.cn/gateway/api")
                .contentType("application/json")
                .send(JSON.toJSONString(params)).body();
        JSONObject res = JSON.parseObject(body);
        log.info("请求结果{}", JSON.toJSONString(res, true));
        return res;
    }

    public String getReqTime() {
        return DateTimeUtils.format(LocalDateTime.now(), "yyyyMMddHHmmss");
    }

    public static String getTimeout(int seconds) {
        return DateTimeUtils.format(LocalDateTime.now().plusSeconds(seconds), "yyyyMMddHHmmss");
    }

    public static String getTimeout(LocalDateTime createTime, int seconds) {
        return DateTimeUtils.format(Optional.ofNullable(createTime).orElse(LocalDateTime.now())
                .plusSeconds(seconds), "yyyyMMddHHmmss");
    }

    public JSONObject requestAlipayRaw(String orderId, BigDecimal amount, String subject,
                                       String timeout, String type, String returnUrl) {
        String pOrderId = paddingOrderId(orderId);
        Map<String, String> params = new HashMap<>();
        params.put("version", "10");
        params.put("mer_no", hmPayProperties.getMerNo());
        params.put("mer_order_no", pOrderId);
        params.put("create_time", getReqTime());
        params.put("expire_time", timeout);
        params.put("order_amt", amount.setScale(2, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString());
        params.put("notify_url", hmPayProperties.getNotifyUrl() + "/" + type + "/" + orderId);
        params.put("return_url", returnUrl);
        params.put("create_ip", "58_213_205_43");
        params.put("goods_name", subject);
        params.put("store_id", "100001");
        params.put("product_code", "01020002");
        params.put("clear_cycle", "0"); //不参与签名
        params.put("meta_option", "[{\"s\":\"Android\",\"n\":\"绿洲宇宙\",\"id\":\"vip.raex.nft\",\"sc\":\"wzsc://\"},{\"s\":\"IOS\",\"n\":\"\",\"id\":\"\",\"sc\":\"\"}]"); //不参与签名
        params.put("jump_scheme", "hemacash://hmpay");
        params.put("sign_type", "RSA");
        params.put("accsplit_info", "N");
        log.info("支付请求参数: {}", JSON.toJSONString(params, true));
        params.put("sign", getSign(params));
        log.info("支付请求签名: {}", params.get("sign"));
        encodeParams(params);

        String body = HttpRequest.post("https://cash.sandgate.cn/gateway/api")
                .contentType("application/json")
                .send(JSON.toJSONString(params)).body();
        JSONObject res = JSON.parseObject(body);
        log.info("支付请求结果: {}", JSON.toJSONString(res, true));
        return res;
    }

    public String requestAlipay(String orderId, BigDecimal amount, String subject,
                                String timeout, String type, String returnUrl) {
        JSONObject res = requestAlipayRaw(orderId, amount, subject, timeout, type, returnUrl);
        if ("000000".equals(res.getString("ret_code"))) {
            redisTemplate.opsForValue()
                    .set(RedisKeys.PAY_TMP + orderId, Constants.PayChannel.HM, 1, TimeUnit.DAYS);
            return res.getJSONObject("data").getString("token_id");
        }
        throw new BusinessException("绿洲宇宙冷却系统已启动，请稍后支付");
    }

    public JSONObject query(String orderId) {
        orderId = paddingOrderId(orderId);
        Map<String, String> bizContent = new HashMap<>();
        bizContent.put("out_order_no", orderId);
        return requestApi("trade.query", bizContent);
    }

    public JSONObject refund(String orderId, BigDecimal amount) {
        orderId = paddingOrderId(orderId);
        Map<String, String> bizContent = new HashMap<>();
        bizContent.put("out_order_no", paddingOrderId(orderId));
        bizContent.put("refund_amount", amount.stripTrailingZeros().toPlainString());
        bizContent.put("refund_request_no", snowflakeIdWorker.nextId() + "");
        return requestApi("trade.refund", bizContent);
    }

    public PayQuery payQuery(String orderId) {
        JSONObject res = query(orderId);
        PayQuery query = new PayQuery(Constants.PayChannel.HM);
        if ("200".equals(res.getString("code"))) {
            query.setExist(true);
            JSONObject data = res.getJSONObject("data");
            String sub_code = data.getString("sub_code");
            if ("ILLEGAL_ORDER".equals(sub_code)) {
                query.setExist(false);
                return query;
            }
            if (data.containsKey("total_amount")) {
                query.setAmount(new BigDecimal(data.getString("total_amount")));
            }
            query.setTransactionId(data.getString("plat_trx_no"));
            switch (sub_code) {
                case "SUCCESS":
                    query.setStatus(PayStatus.SUCCESS);
                    break;
                case "FINISH":
                case "FAILED":
                    query.setStatus(PayStatus.FAIL);
                    break;
                case "CREATED":
                case "WAITING_PAYMENT":
                    query.setStatus(PayStatus.PENDING);
                    break;
                case "CLOSED":
                case "CANCELED":
                    query.setStatus(PayStatus.CANCEL);
                    break;
            }
            if (Objects.equals(Boolean.TRUE, data.getBoolean("is_refund"))) {
                query.setStatus(PayStatus.REFUNDED);
            }
            String successTime = data.getString("success_time");
            if (StringUtils.isNotEmpty(successTime)) {
                query.setPayTime(LocalDateTime.parse(successTime, DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
            }
        } else {
            query.setExist(false);
            query.setMsg(res.getString("msg"));
        }
        return query;
    }
}
