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

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

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * 北森API调用工具类（单例模式）
 */
public class BeisenApiClient {
    private static final Logger logger = Logger.getLogger(BeisenApiClient.class);
    private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");
    private static final int MAX_RETRIES = 3;

    // 单例实例
    private static volatile BeisenApiClient instance;

    private final OkHttpClient httpClient;
    private final BeisenTokenManager tokenManager;

    // 私有构造函数
    private BeisenApiClient() {
        this.httpClient = buildHttpClient();
        this.tokenManager = BeisenTokenManager.getInstance();
    }

    /**
     * 获取单例实例
     */
    public static BeisenApiClient getInstance() {
        if (instance == null) {
            synchronized (BeisenApiClient.class) {
                if (instance == null) {
                    instance = new BeisenApiClient();
                }
            }
        }
        return instance;
    }


    /**
     * api调用qun
     *
     * @param apiUrl
     * @param requestData
     * @return
     * @throws IOException
     */
    public JSONObject callApi(String apiUrl, JSONObject requestData) throws IOException {
        return callApi(apiUrl, requestData, "POST");
    }

    public JSONObject callApi(String apiUrl, JSONArray dataArray) throws IOException {
        return callApi(apiUrl, dataArray, "POST");
    }

    public JSONObject callPutApi(String apiUrl, JSONObject requestData) throws IOException {
        return callApi(apiUrl, requestData, "PUT");
    }

    public JSONObject callGetApi(String apiUrl, JSONObject requestData) throws IOException {
        return callApi(apiUrl, requestData, "GET");
    }

    public JSONObject callGetApi(String apiUrl, JSONArray dataArray) throws IOException {
        return callApi(apiUrl, dataArray, "GET");
    }

    /**
     * api调用
     *
     * @param apiUrl
     * @param requestData
     * @return
     * @throws IOException
     */
    public JSONObject callApi(String apiUrl, JSONObject requestData, String method) throws IOException {
        return callApiInternal(apiUrl, requestData, true, method);
    }

    public JSONObject callApi(String apiUrl, JSONArray dataArray, String method) throws IOException {
        return callApiInternal(apiUrl, dataArray, true, method);
    }


    // 核心API调用逻辑----------------------------------------------------------

    /**
     * 通用API调用方法
     *
     * @param requestBody 支持JSONObject/JSONArray
     */
    private JSONObject callApiInternal(String apiUrl, Object requestBody, boolean retryOnTokenExpired, String method)
            throws IOException {
        int retryCount = 0;
        JSONObject handleResponse = new JSONObject();
        while (retryCount <= MAX_RETRIES) {
            try {
                String accessToken = tokenManager.getAccessToken();
                Request request = buildRequest(apiUrl, requestBody, accessToken, method);

                try (Response response = httpClient.newCall(request).execute()) {
                    handleResponse = handleResponse(response, apiUrl, method);
                    return handleResponse;
                }
            } catch (IOException e) {
                if (shouldRetry(retryOnTokenExpired, retryCount, e)) {
                    retryCount++;
                    handleTokenRefresh(retryCount);
                    continue;
                }else {
                	retryCount++;
                	break;
                }
            }
        }
        return handleResponse;
        //不抛出,使用调用的地方存储msg
//        throw new IOException("API request failed after " + MAX_RETRIES + " attempts");
    }

    // 响应处理逻辑-------------------------------------------------------------

    private JSONObject handleResponse(Response response, String apiUrl, String method) throws IOException {
    	String responseBody = response.body() != null ? response.body().string() : "null";
     
        if (!response.isSuccessful()) {
            handleHttpError(responseBody, apiUrl, method);

        }

      
        JSONObject jsonResponse = parseResponse(responseBody, apiUrl);

        validateBusinessStatus(jsonResponse, apiUrl);
        return jsonResponse;
    }

    private void handleHttpError(String errorBody, String apiUrl, String method) throws IOException {
        // 输出日志
        logger.error("API request failed "+
                ", URL: " + apiUrl + ", Method: " + method +
                ", Body: " + errorBody);
        System.out.print("调用错误"+errorBody);
//        throw new IOException("HTTP error: " + response.code());
    }

    private JSONObject parseResponse(String responseBody, String apiUrl) throws IOException {
        try {
            return JSON.parseObject(responseBody);
        } catch (Exception e) {
            logger.error("Invalid JSON response from API. URL: " + apiUrl + ", Response: " + responseBody);
            throw new IOException("Failed to parse API response", e);
        }
    }

    private void validateBusinessStatus(JSONObject jsonResponse, String apiUrl) throws IOException {
        int apiCode = jsonResponse.getIntValue("code");
        if (apiCode != 200) {
            String apiMessage = jsonResponse.getString("message");
            logger.error("API business error. Code: " + apiCode +
                    ", Message: " + apiMessage + ", URL: " + apiUrl);
//            throw new IOException("Business error: " + apiMessage + " (code: " + apiCode + ")");
        }
    }

    // Token重试逻辑-----------------------------------------------------------

    private boolean shouldRetry(boolean retryOnTokenExpired, int retryCount, Exception e) {
        return retryOnTokenExpired &&
                isTokenExpiredError(e) &&
                retryCount < MAX_RETRIES;
    }

    private void handleTokenRefresh(int retryCount) {
        logger.warn("Token may have expired, refreshing and retrying... (" +
                retryCount + "/" + MAX_RETRIES + ")");
        try {
            tokenManager.refreshToken();
        } catch (IOException ex) {
            logger.error("Failed to refresh token during retry", ex);
        }
    }

    // 请求构建----------------------------------------------------------------

    private Request buildRequest(String apiUrl, Object requestBody, String accessToken, String method) {
        String jsonString = convertToJsonString(requestBody);
        // 添加日志输出
        System.out.print("Request接口请求参数"+jsonString);
        RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, jsonString);
        if (requestBody instanceof JSONObject) {
            if (((JSONObject) requestBody).size() == 0) {
                body = null;
            }
        }
        return new Request.Builder()
                .url(apiUrl)
                .addHeader("Authorization", "Bearer " + accessToken)
                .addHeader("Content-Type", "application/json")
                .method(method, body)
                .build();
    }

    private String convertToJsonString(Object data) {
        if (data instanceof JSONObject) {
            return ((JSONObject) data).toJSONString();
        } else if (data instanceof JSONArray) {
            return ((JSONArray) data).toJSONString();
        }
        throw new IllegalArgumentException("Unsupported request data type: " + data.getClass());
    }

    // 辅助方法---------------------------------------------------------------

    private boolean isTokenExpiredError(Exception e) {
        String msg = e.getMessage();
        return msg != null && (msg.contains("401") ||
                msg.contains("Unauthorized") ||
                msg.contains("token"));
    }

    private OkHttpClient buildHttpClient() {
        return new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .writeTimeout(15, TimeUnit.SECONDS)
                .readTimeout(45, TimeUnit.SECONDS)
                .connectionPool(new ConnectionPool(50, 5, TimeUnit.MINUTES))
                .retryOnConnectionFailure(true)
                .build();
    }
}
