package com.jeesharp.business.modules.member.huawei;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.jeesharp.business.modules.member.config.MemberIapProperties;
import com.jeesharp.framework.exception.BizErrorException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;

/**
 * 华为 IAP Order 服务远程调用：应用级 AccessToken、验单、消耗型确认购买。
 * <p>接口说明见华为文档：验证购买 Token、确认购买（消耗型）。</p>
 *
 * @author jeesharp
 * @since 2026-04-12
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class HuaweiIapOrderRemoteClient {

    private static final Duration HTTP_TIMEOUT = Duration.ofSeconds(20);

    private final MemberIapProperties memberIapProperties;

    private final HttpClient httpClient = HttpClient.newBuilder()
            .connectTimeout(HTTP_TIMEOUT)
            .build();

    private final Object tokenLock = new Object();
    private volatile String cachedAccessToken;
    private volatile long tokenDeadlineEpochMs;

    /**
     * 调用华为 Order 服务验证 purchaseToken。
     */
    public HuaweiPurchaseVerificationResult verifyPurchaseToken(String purchaseToken, String productId) {
        assertConfiguredForRemote();
        if (StrUtil.isBlank(purchaseToken) || StrUtil.isBlank(productId)) {
            HuaweiPurchaseVerificationResult r = new HuaweiPurchaseVerificationResult();
            r.setSuccess(false);
            r.setResponseCode("-1");
            r.setResponseMessage("purchaseToken 或 productId 为空");
            return r;
        }
        String at = getOrRefreshAppAccessToken();
        String root = trimTrailingSlash(memberIapProperties.getOrderRootUrl());
        String url = root + "/applications/purchases/tokens/verify";
        String body = JSONUtil.createObj()
                .set("purchaseToken", purchaseToken)
                .set("productId", productId)
                .toString();
        String auth = Base64.getEncoder().encodeToString(("APPAT:" + at).getBytes(StandardCharsets.UTF_8));
        try {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(url))
                    .timeout(HTTP_TIMEOUT)
                    .header("Content-Type", "application/json; charset=UTF-8")
                    .header("Authorization", "Basic " + auth)
                    .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
                    .build();
            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
            String respBody = response.body();
            if (response.statusCode() != 200) {
                log.warn("Huawei IAP verify HTTP status={} body={}", response.statusCode(), respBody);
                HuaweiPurchaseVerificationResult r = new HuaweiPurchaseVerificationResult();
                r.setSuccess(false);
                r.setResponseCode(String.valueOf(response.statusCode()));
                r.setResponseMessage("华为验单 HTTP 非200");
                r.setRawBody(respBody);
                return r;
            }
            return parseVerifyResponse(respBody);
        } catch (BizErrorException e) {
            throw e;
        } catch (Exception e) {
            log.error("Huawei IAP verify request failed: {}", e.getMessage());
            throw new BizErrorException("调用华为验单接口失败：" + e.getMessage());
        }
    }

    /**
     * 消耗型商品发货成功后，通知华为完成确认/消耗（失败仅打日志，不抛业务异常）。
     */
    public void confirmPurchaseQuietly(String purchaseToken, String productId) {
        if (!memberIapProperties.isConfirmAfterFulfill()) {
            return;
        }
        try {
            assertConfiguredForRemote();
            if (StrUtil.isBlank(purchaseToken) || StrUtil.isBlank(productId)) {
                return;
            }
            String at = getOrRefreshAppAccessToken();
            String root = trimTrailingSlash(memberIapProperties.getOrderRootUrl());
            String url = root + "/applications/v2/purchases/confirm";
            String body = JSONUtil.createObj()
                    .set("purchaseToken", purchaseToken)
                    .set("productId", productId)
                    .toString();
            String auth = Base64.getEncoder().encodeToString(("APPAT:" + at).getBytes(StandardCharsets.UTF_8));
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(url))
                    .timeout(HTTP_TIMEOUT)
                    .header("Content-Type", "application/json; charset=UTF-8")
                    .header("Authorization", "Basic " + auth)
                    .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
                    .build();
            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
            String respBody = response.body();
            if (response.statusCode() != 200) {
                log.warn("Huawei IAP confirm HTTP status={} body={}", response.statusCode(), respBody);
                return;
            }
            JSONObject jo = JSONUtil.parseObj(respBody);
            String code = stringifyCode(jo.get("responseCode"));
            if (!"0".equals(code)) {
                log.warn("Huawei IAP confirm failed responseCode={} message={} body={}", code, jo.getStr("responseMessage"), respBody);
            }
        } catch (Exception e) {
            log.error("Huawei IAP confirm failed: {}", e.getMessage());
        }
    }

    private HuaweiPurchaseVerificationResult parseVerifyResponse(String respBody) {
        HuaweiPurchaseVerificationResult r = new HuaweiPurchaseVerificationResult();
        r.setRawBody(respBody);
        JSONObject jo = JSONUtil.parseObj(respBody);
        String code = stringifyCode(jo.get("responseCode"));
        r.setResponseCode(code);
        r.setResponseMessage(jo.getStr("responseMessage"));
        if (!"0".equals(code)) {
            r.setSuccess(false);
            return r;
        }
        String ptd = jo.getStr("purchaseTokenData");
        if (StrUtil.isBlank(ptd)) {
            r.setSuccess(false);
            r.setResponseMessage("华为返回缺少 purchaseTokenData");
            return r;
        }
        JSONObject data = JSONUtil.parseObj(ptd);
        HuaweiIapParsedPurchase p = new HuaweiIapParsedPurchase();
        p.setOrderId(data.getStr("orderId"));
        p.setPurchaseToken(data.getStr("purchaseToken"));
        p.setProductId(data.getStr("productId"));
        p.setPurchaseState(readInteger(data, "purchaseState"));
        p.setConsumptionState(readInteger(data, "consumptionState"));
        p.setKind(readInteger(data, "kind"));
        Long pm = data.getLong("purchaseTimeMillis");
        if (pm == null) {
            pm = data.getLong("purchaseTime");
        }
        p.setPurchaseTimeMillis(pm);
        p.setApplicationId(data.getLong("applicationId"));
        r.setPurchase(p);
        r.setSuccess(true);
        return r;
    }

    private static String stringifyCode(Object code) {
        if (code == null) {
            return "";
        }
        return String.valueOf(code);
    }

    private void assertConfiguredForRemote() {
        if (StrUtil.isBlank(memberIapProperties.getClientId()) || StrUtil.isBlank(memberIapProperties.getClientSecret())) {
            throw new BizErrorException("未配置华为 IAP：请在 jeesharp.member.iap 中设置 client-id、client-secret、order-root-url");
        }
        if (StrUtil.isBlank(memberIapProperties.getOrderRootUrl())) {
            throw new BizErrorException("未配置华为 IAP order-root-url");
        }
    }

    private String getOrRefreshAppAccessToken() {
        long now = System.currentTimeMillis();
        if (StrUtil.isNotBlank(cachedAccessToken) && now < tokenDeadlineEpochMs - 60_000L) {
            return cachedAccessToken;
        }
        synchronized (tokenLock) {
            if (StrUtil.isNotBlank(cachedAccessToken) && now < tokenDeadlineEpochMs - 60_000L) {
                return cachedAccessToken;
            }
            String tokenUrl = memberIapProperties.getOauthTokenUrl();
            String form = "grant_type=client_credentials"
                    + "&client_id=" + urlEncode(memberIapProperties.getClientId())
                    + "&client_secret=" + urlEncode(memberIapProperties.getClientSecret());
            try {
                HttpRequest request = HttpRequest.newBuilder()
                        .uri(URI.create(tokenUrl))
                        .timeout(HTTP_TIMEOUT)
                        .header("Content-Type", "application/x-www-form-urlencoded")
                        .POST(HttpRequest.BodyPublishers.ofString(form, StandardCharsets.UTF_8))
                        .build();
                HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
                if (response.statusCode() != 200) {
                    log.warn("Huawei OAuth token HTTP status={} body={}", response.statusCode(), response.body());
                    throw new BizErrorException("获取华为应用级 AccessToken 失败，HTTP " + response.statusCode());
                }
        JSONObject jo = JSONUtil.parseObj(response.body());
                String at = jo.getStr("access_token");
                if (StrUtil.isBlank(at)) {
                    throw new BizErrorException("华为 OAuth 未返回 access_token：" + response.body());
                }
                Long exp = jo.getLong("expires_in");
                long expiresInSec = exp == null ? 3600L : exp;
                cachedAccessToken = at;
                tokenDeadlineEpochMs = System.currentTimeMillis() + expiresInSec * 1000L;
                return at;
            } catch (BizErrorException e) {
                throw e;
            } catch (Exception e) {
                log.error("Huawei OAuth token request failed: {}", e.getMessage());
                throw new BizErrorException("获取华为应用级 AccessToken 失败：" + e.getMessage());
            }
        }
    }

    private static String urlEncode(String s) {
        return URLEncoder.encode(s == null ? "" : s, StandardCharsets.UTF_8);
    }

    private static String trimTrailingSlash(String url) {
        if (url == null) {
            return "";
        }
        String u = url.trim();
        while (u.endsWith("/")) {
            u = u.substring(0, u.length() - 1);
        }
        return u;
    }

    private static Integer readInteger(JSONObject data, String key) {
        if (data == null || !data.containsKey(key)) {
            return null;
        }
        Object v = data.get(key);
        if (v == null) {
            return null;
        }
        return Integer.parseInt(String.valueOf(v));
    }
}
