package com.kingdee.eas.custom.weaverTodo.client;

import com.alibaba.fastjson.JSONObject;
import com.kingdee.eas.custom.weaverTodo.config.WeaverTodoConfig;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 泛微开放平台 access_token（与用户 SSO oauth 凭证分离）。
 * token 按 host+corpid+appKey 做 JVM 级缓存，避免每次 new Dao 都重新 authorize。
 */
public class WeaverOpenApiTokenManager {
    private static final Logger logger = Logger.getLogger(WeaverOpenApiTokenManager.class);

    private static final Object TOKEN_LOCK = new Object();
    private static volatile String cachedKey;
    private static volatile String cachedToken;
    private static volatile long cachedExpireAtMs;

    private static final int AUTH_RETRY_TIMES = 3;
    private static final long AUTH_RETRY_BASE_MS = 800L;

    private final WeaverTodoConfig config;

    public WeaverOpenApiTokenManager(WeaverTodoConfig config) {
        this.config = config;
    }

    public String getAccessToken() throws Exception {
        String key = cacheKey();
        long now = System.currentTimeMillis();
        if (key.equals(cachedKey) && StringUtils.isNotBlank(cachedToken) && now < cachedExpireAtMs - 60_000L) {
            logger.info("OpenAPI token 缓存命中(JVM), leftMs=" + (cachedExpireAtMs - now));
            return cachedToken;
        }
        synchronized (TOKEN_LOCK) {
            now = System.currentTimeMillis();
            if (key.equals(cachedKey) && StringUtils.isNotBlank(cachedToken) && now < cachedExpireAtMs - 60_000L) {
                logger.info("OpenAPI token 缓存命中(JVM), leftMs=" + (cachedExpireAtMs - now));
                return cachedToken;
            }
            logger.info("OpenAPI token 开始刷新 host=" + config.getOpenapiHost()
                    + " authorizePath=" + config.getAuthorizePath()
                    + " tokenPath=" + config.getAccessTokenPath()
                    + " corpid=" + config.getCorpid()
                    + " app_key=" + config.getAppKey());
            String code = fetchAuthCodeWithRetry();
            logger.info("OpenAPI authorize 取得 code, len=" + (code == null ? 0 : code.length()));
            JSONObject tokenBody = exchangeToken(code);
            String token = firstNonBlank(tokenBody.getString("accessToken"), tokenBody.getString("acessToken"));
            if (StringUtils.isBlank(token)) {
                throw new IllegalStateException("获取 accessToken 失败: " + tokenBody);
            }
            int expiresIn = tokenBody.getIntValue("expires_in");
            if (expiresIn <= 0) {
                expiresIn = 7200;
            }
            cachedKey = key;
            cachedToken = token;
            cachedExpireAtMs = System.currentTimeMillis() + expiresIn * 1000L;
            logger.info("Weaver OpenAPI accessToken 刷新成功, expiresIn=" + expiresIn
                    + " tokenPrefix=" + token.substring(0, Math.min(8, token.length())) + "...");
            return cachedToken;
        }
    }

    private String cacheKey() {
        return StringUtils.defaultString(config.getOpenapiHost())
                + "|" + StringUtils.defaultString(config.getCorpid())
                + "|" + StringUtils.defaultString(config.getAppKey());
    }

    private String fetchAuthCodeWithRetry() throws Exception {
        Exception last = null;
        for (int i = 1; i <= AUTH_RETRY_TIMES; i++) {
            try {
                return fetchAuthCode();
            } catch (Exception e) {
                last = e;
                String msg = e.getMessage() == null ? "" : e.getMessage();
                if (!isRetryableAuthFailure(msg) || i >= AUTH_RETRY_TIMES) {
                    throw e;
                }
                long sleepMs = AUTH_RETRY_BASE_MS * i;
                logger.warn("OpenAPI authorize 可重试失败 attempt=" + i + "/" + AUTH_RETRY_TIMES
                        + " sleepMs=" + sleepMs + " err=" + msg);
                try {
                    Thread.sleep(sleepMs);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw e;
                }
            }
        }
        throw last != null ? last : new IllegalStateException("authorize 失败");
    }

    private static boolean isRetryableAuthFailure(String msg) {
        return msg.contains("DegradeException")
                || msg.contains("SentinelBlockException")
                || msg.contains("\"errcode\":\"-10\"")
                || msg.contains("\"errcode\":-10");
    }

    private String fetchAuthCode() throws Exception {
        String url = config.getOpenapiHost() + config.getAuthorizePath()
                + "?corpid=" + config.getCorpid()
                + "&response_type=code&state=shrTodo";
        logger.info("OpenAPI authorize GET url=" + url);
        String body = WeaverHttpHelper.get(url, 15000, 30000);
        assertJsonApiBody("authorize", body);
        JSONObject json = JSONObject.parseObject(body);
        // 兼容 {errcode:0} 与 {message:{errcode:-10}}
        Object err = json.get("errcode");
        if (err == null && json.getJSONObject("message") != null) {
            err = json.getJSONObject("message").get("errcode");
        }
        if (err != null && !"0".equals(String.valueOf(err))) {
            throw new IllegalStateException("authorize 业务失败: " + body);
        }
        String code = json.getString("code");
        if (StringUtils.isBlank(code) && json.getJSONObject("message") != null) {
            code = json.getJSONObject("message").getString("code");
        }
        if (StringUtils.isBlank(code)) {
            throw new IllegalStateException("authorize 未返回 code: " + body);
        }
        return code;
    }

    private JSONObject exchangeToken(String code) throws Exception {
        String url = config.getOpenapiHost() + config.getAccessTokenPath();
        Map<String, String> form = new LinkedHashMap<String, String>();
        form.put("app_key", config.getAppKey());
        form.put("app_secret", config.getAppSecret());
        form.put("grant_type", "authorization_code");
        form.put("code", code);
        logger.info("OpenAPI access_token POST url=" + url
                + " grant_type=authorization_code app_key=" + config.getAppKey()
                + " codeLen=" + (code == null ? 0 : code.length()));
        String body = WeaverHttpHelper.postForm(url, form, 15000, 30000);
        assertJsonApiBody("access_token", body);
        JSONObject json = JSONObject.parseObject(body);
        if (!"0".equals(String.valueOf(json.get("errcode")))) {
            throw new IllegalStateException("access_token 业务失败: " + body);
        }
        return json;
    }

    private static void assertJsonApiBody(String step, String body) {
        if (WeaverHttpHelper.looksLikeHtml(body)) {
            throw new IllegalStateException(step
                    + " 返回 HTML（门户页面），不是开放平台 JSON。"
                    + " 请确认 weaver.openapi.host 是 E10 OPENAPI 域名/端口，而不是前端 portal；"
                    + " 也可在 properties 调整 weaver.openapi.authorizePath。"
                    + " bodySnippet=" + WeaverHttpHelper.snippet(body));
        }
        String trim = body == null ? "" : body.trim();
        if (!trim.startsWith("{") && !trim.startsWith("[")) {
            throw new IllegalStateException(step
                    + " 响应不是 JSON。bodySnippet=" + WeaverHttpHelper.snippet(body));
        }
    }

    private static String firstNonBlank(String a, String b) {
        return StringUtils.isNotBlank(a) ? a : b;
    }
}
