package com.kingdee.eas.custom.beisen.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import okhttp3.*;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BeisenTokenManager {
    private static final Logger logger = Logger.getLogger(BeisenTokenManager.class);
    private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");
    private static final long EXPIRY_BUFFER = 300_000; // 5分钟缓冲时间

    private final OkHttpClient httpClient;
    private final Lock lock = new ReentrantLock();
    private volatile TokenState tokenState;

    private final String appKey;
    private final String appSecret;
    private final String tokenUrl;

    // 添加单例实例
    private static volatile BeisenTokenManager instance;

    // 私有化构造器
    private BeisenTokenManager() {
        this(Config.APP_KEY, Config.APP_SECRET, Config.TOKEN_URL);
    }


    // 获取单例方法
    public static BeisenTokenManager getInstance() {
        if (instance == null) {
            synchronized (BeisenTokenManager.class) {
                if (instance == null) {
                    instance = new BeisenTokenManager();
                }
            }
        }
        return instance;
    }

    public BeisenTokenManager(String appKey, String appSecret, String tokenUrl) {
        this.appKey = appKey;
        this.appSecret = appSecret;
        this.tokenUrl = tokenUrl;
        this.httpClient = buildHttpClient();
        this.tokenState = loadInitialToken();
    }

    public String getAccessToken() throws IOException {
        TokenState currentState = tokenState;
        if (currentState != null && !isTokenExpired(currentState)) {
            return currentState.getAccessToken();
        }

        if (currentState == null || isTokenExpired(currentState)) {
            lock.lock();
            try {
                currentState = tokenState;
                if (currentState == null || isTokenExpired(currentState)) {
                    currentState = fetchNewToken();
                    tokenState = currentState;
                }
            } finally {
                lock.unlock();
            }
        }
        return currentState.getAccessToken();
    }

    public void refreshToken() throws IOException {
        TokenState currentState = tokenState;
        if (currentState == null || currentState.getRefreshToken() == null) {
            tokenState = fetchNewToken();
            return;
        }

        if (isTokenExpired(currentState)) {
            lock.lock();
            try {
                currentState = tokenState;
                if (isTokenExpired(currentState)) {
                    tokenState = refreshToken(currentState.getRefreshToken());
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private boolean isTokenExpired(TokenState state) {
        // 添加缓冲时间，避免在token即将过期时使用
        return System.currentTimeMillis() > (state.getExpireTime() - EXPIRY_BUFFER);
    }

    private TokenState loadInitialToken() {
        try {
            return fetchNewToken();
        } catch (Exception e) {
            logger.error("Failed to initialize token", e);
            throw new RuntimeException("Token initialization failed", e);
        }
    }

    private TokenState fetchNewToken() throws IOException {
        JSONObject requestBody = new JSONObject();
        requestBody.put("grant_type", "client_credentials");
        requestBody.put("app_key", appKey);
        requestBody.put("app_secret", appSecret);

        return executeTokenRequest(requestBody);
    }

    private TokenState refreshToken(String refreshToken) throws IOException {
        JSONObject requestBody = new JSONObject();
        requestBody.put("grant_type", "refresh_token");
        requestBody.put("refresh_token", refreshToken);

        return executeTokenRequest(requestBody);
    }

    private TokenState executeTokenRequest(JSONObject requestBody) throws IOException {
        Request request = new Request.Builder().url(tokenUrl).post(RequestBody.create(JSON_MEDIA_TYPE, requestBody.toJSONString())).build();

        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                String errorBody = response.body() != null ? response.body().string() : "null";
                logger.error("Token request failed. Code: " + response.code() + ", Body: " + errorBody);
                throw new IOException("Token request failed with code: " + response.code());
            }

            String responseBody = response.body().string();
            JSONObject jsonResponse = JSON.parseObject(responseBody);

            String accessToken = jsonResponse.getString("access_token");
            String refreshToken = jsonResponse.getString("refresh_token");
            Long expiresIn = jsonResponse.getLong("expires_in");

            // 更健壮的空值检查
            if (accessToken == null || expiresIn == null || expiresIn <= 0) {
                throw new IOException("Invalid token response: " + responseBody);
            }

            return new TokenState(accessToken, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiresIn), refreshToken);
        }
    }

    private OkHttpClient buildHttpClient() {
        return new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).retryOnConnectionFailure(true).build();
    }

    private static class TokenState {
        private final String accessToken;
        private final long expireTime;
        private final String refreshToken;

        public TokenState(String accessToken, long expireTime, String refreshToken) {
            this.accessToken = accessToken;
            this.expireTime = expireTime;
            this.refreshToken = refreshToken;
        }

        public String getAccessToken() {
            return accessToken;
        }

        public long getExpireTime() {
            return expireTime;
        }

        public String getRefreshToken() {
            return refreshToken;
        }
    }

    public static class Config {
        private static final String BESON_CONFIG_PATH = "/server/properties/beisen/BeiSenConfig.properties";

        // public static String APP_KEY = "FFEC5C62AB444567AFC11E8D394CC072";
        // public static String APP_SECRET = "74FE75BEE476496EAF531C2219EC10E57EDCE01D74F64557BADEEC78FBFF3B26";
        // public static String TOKEN_URL = "https://openapi.italent.cn/token";
        // 测试环境
        // public static String APP_KEY = "FFEC5C62AB444567AFC11E8D394CC072";
        // public static String APP_SECRET = "74FE75BEE476496EAF531C2219EC10E57EDCE01D74F64557BADEEC78FBFF3B26";
        // 生产环境
        // public static String APP_KEY = "C16FE3483B4E42FCA97686B6D01F1B3C";
        // public static String APP_SECRET = "DE0EED6E741149A3842BAA89143415B3D5C76FC304AB4FCF8D4D708B8D1F1E04";
        public static String TOKEN_URL = "https://openapi.italent.cn/token";
        public static String APP_KEY = "";
        public static String APP_SECRET = "";

        static {
            // 从配置文件中读取配置
            try {
                BeisenParamByPropertiesUtil beisenParamByProperties = new BeisenParamByPropertiesUtil(BESON_CONFIG_PATH);
                Map<String, String> config = beisenParamByProperties.getConfig();
                // 如果配置文件中存在配置，则覆盖默认配置
                if (config != null) {
                    // 如果配置文件中存在appKey，则覆盖默认appKey
                    if (config.containsKey("APP_KEY")) {
                        APP_KEY = config.get("APP_KEY");
                    }
                    // 如果配置文件中存在appSecret，则覆盖默认appSecret
                    if (config.containsKey("APP_SECRET")) {
                        APP_SECRET = config.get("APP_SECRET");
                    }
                    // 如果配置文件中存在tokenUrl，则覆盖默认tokenUrl
                    if (config.containsKey("ACCESSTOKEN_URL")) {
                        TOKEN_URL = config.get("ACCESSTOKEN_URL");
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}