package com.zcloud.personnel.positioning.integration.finds;

import com.alibaba.cola.exception.BizException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import com.zcloud.personnel.positioning.persistence.dataobject.OpenapiConfigDO;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;

/**
 * FindS OpenAPI signed client.
 *
 * @Author fangjiakai
 * @Date 2026-07-07
 */
@Component
@AllArgsConstructor
public class FindsOpenApiClient {
    private static final String SIK_PATH_SEGMENT = "sik";
    private static final int CONNECT_TIMEOUT_MS = 10000;
    private static final int READ_TIMEOUT_MS = 60000;
    private static final int MAX_REQUEST_ATTEMPTS = 3;

    private final OpenapiConfigCache openapiConfigCache;
    private final ObjectMapper objectMapper;

    public JsonNode post(String apiName, Map<String, Object> request) {
        OpenapiConfigDO config = loadEnabledConfig();
        return post(config, apiName, request);
    }

    public JsonNode post(OpenapiConfigDO config, String apiName, Map<String, Object> request) {
        validateConfig(config);
        String requestJson = writeJson(request == null ? Collections.emptyMap() : request);
        String timestamp = String.valueOf(System.currentTimeMillis());
        String externalPrefix = normalizePrefix(defaultIfBlank(config.getExternalPrefix(), "/rest/"));
        String internalPrefix = normalizePrefix(defaultIfBlank(config.getInternalPrefix(), "/rest/"));
        String signPath = buildApiPath(externalPrefix, apiName, config.getSik());
        String requestPath = buildApiPath(internalPrefix, apiName, config.getSik());
        String signature = sign(signPath, requestJson, config.getSisCipherText(), timestamp);
        String url = trimTrailingSlash(config.getHostUrl()) + requestPath + "?_sign=" + signature;
        String response = doPost(url, requestJson, timestamp);
        try {
            return objectMapper.readTree(response);
        } catch (IOException e) {
            throw new BizException("FindS接口响应不是合法JSON：" + apiName);
        }
    }

    private OpenapiConfigDO loadEnabledConfig() {
        SSOUser currentUser = AuthContext.getCurrentUser();
        Long companyId = currentUser == null ? null : currentUser.getCompanyId();
        OpenapiConfigDO config = openapiConfigCache.getEnabledConfig(companyId);
        if (config == null) {
            throw new BizException("未配置启用的FindS OpenAPI配置");
        }
        validateConfig(config);
        return config;
    }

    private void validateConfig(OpenapiConfigDO config) {
        if (!StringUtils.hasText(config.getHostUrl())) {
            throw new BizException("FindS OpenAPI hostUrl未配置");
        }
        if (!StringUtils.hasText(config.getSik())) {
            throw new BizException("FindS OpenAPI sik未配置");
        }
        if (!StringUtils.hasText(config.getSisCipherText())) {
            throw new BizException("FindS OpenAPI sis未配置");
        }
    }

    private String buildApiPath(String prefix, String apiName, String sik) {
        return prefix + apiName + "/" + SIK_PATH_SEGMENT + "/" + sik;
    }

    private String sign(String path, String requestJson, String sis, String timestamp) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(sis.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            mac.update(path.getBytes(StandardCharsets.UTF_8));
            mac.update("request".getBytes(StandardCharsets.UTF_8));
            mac.update(requestJson.getBytes(StandardCharsets.UTF_8));
            mac.update(timestamp.getBytes(StandardCharsets.UTF_8));
            return toUpperHex(mac.doFinal());
        } catch (Exception e) {
            throw new BizException("FindS OpenAPI签名失败");
        }
    }

    private String doPost(String url, String requestJson, String timestamp) {
        IOException lastException = null;
        for (int attempt = 1; attempt <= MAX_REQUEST_ATTEMPTS; attempt++) {
            HttpURLConnection connection = null;
            try {
                byte[] body = ("request=" + URLEncoder.encode(requestJson, "UTF-8"))
                        .getBytes(StandardCharsets.UTF_8);
                connection = (HttpURLConnection) new URL(url).openConnection();
                connection.setConnectTimeout(CONNECT_TIMEOUT_MS);
                connection.setReadTimeout(READ_TIMEOUT_MS);
                connection.setRequestMethod("POST");
                connection.setDoOutput(true);
                connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
                connection.setRequestProperty("wz-acs-timestamp", timestamp);
                connection.setRequestProperty("Accept", "application/json");
                try (OutputStream outputStream = connection.getOutputStream()) {
                    outputStream.write(body);
                }
                int status = connection.getResponseCode();
                String response = readResponse(status >= 400
                        ? connection.getErrorStream() : connection.getInputStream());
                if (status != HttpURLConnection.HTTP_OK) {
                    throw new BizException("FindS OpenAPI HTTP调用失败，status=" + status + "，body=" + response);
                }
                return response;
            } catch (IOException e) {
                lastException = e;
                if (attempt < MAX_REQUEST_ATTEMPTS) {
                    sleepBeforeRetry(attempt);
                }
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }
        throw new BizException("FindS OpenAPI连接失败：" + (lastException == null
                ? "unknown error" : lastException.getMessage()));
    }

    private void sleepBeforeRetry(int attempt) {
        try {
            Thread.sleep(500L * attempt);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BizException("FindS OpenAPI请求重试被中断");
        }
    }

    private String readResponse(InputStream inputStream) throws IOException {
        if (inputStream == null) {
            return "";
        }
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int len;
        while ((len = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, len);
        }
        return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
    }

    private String writeJson(Object value) {
        try {
            return objectMapper.writeValueAsString(value);
        } catch (IOException e) {
            throw new BizException("FindS OpenAPI请求JSON转换失败");
        }
    }

    private String toUpperHex(byte[] bytes) {
        char[] digits = "0123456789ABCDEF".toCharArray();
        char[] result = new char[bytes.length * 2];
        for (int i = 0; i < bytes.length; i++) {
            result[i * 2] = digits[(bytes[i] & 0xF0) >> 4];
            result[i * 2 + 1] = digits[bytes[i] & 0x0F];
        }
        return new String(result);
    }

    private String normalizePrefix(String prefix) {
        String value = prefix.trim();
        if (!value.startsWith("/")) {
            value = "/" + value;
        }
        if (!value.endsWith("/")) {
            value = value + "/";
        }
        return value;
    }

    private String trimTrailingSlash(String value) {
        String result = value.trim();
        while (result.endsWith("/")) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    private String defaultIfBlank(String value, String defaultValue) {
        return StringUtils.hasText(value) ? value : defaultValue;
    }
}
