package com.usky.vpp.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.usky.common.core.exception.BusinessException;
import com.usky.vpp.config.VppUnProperties;
import com.usky.vpp.config.VppUnPropertiesRegistry;
import com.usky.vpp.config.VppUnTenantContext;
import com.usky.vpp.crypto.VppUnCryptoService;
import com.usky.vpp.crypto.VppUnSmCryptoUtil;
import com.usky.vpp.util.VppUnCipherPayloadHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

/**
 * 向 UN 发送 HTTP 请求（可选国密加密 / Token 认证）
 */
@Component
public class VppUnHttpExecutor {

    private static final Logger log = LoggerFactory.getLogger(VppUnHttpExecutor.class);

    /** Token 获取接口使用明文，不参与 SM2/SM3 加解密。 */
    private static final String TOKEN_REQUEST = "TokenRequest";

    @Autowired
    private VppUnPropertiesRegistry propertiesRegistry;

    @Autowired
    private VppUnCryptoService cryptoService;

    @Autowired
    @Qualifier("vppUnRestTemplate")
    private RestTemplate restTemplate;

    @Lazy
    @Autowired
    private VppUnTokenHolder tokenHolder;

    @Autowired
    private ObjectMapper objectMapper;

    public String postRaw(String serviceName, Map<String, Object> body, boolean withToken) {
        try {
            return executePost(serviceName, body, withToken);
        } catch (BusinessException ex) {
            if (withToken && isTokenError(ex)) {
                log.info("Token 失效，刷新后重试 {}", serviceName);
                tokenHolder.invalidate();
                tokenHolder.getToken();
                return executePost(serviceName, body, withToken);
            }
            throw ex;
        }
    }

    private String executePost(String serviceName, Map<String, Object> body, boolean withToken) {
        VppUnProperties properties = propertiesRegistry.getCurrent();
        if (!properties.isOutboundActive()) {
            throw new BusinessException("运管平台 outbound 未启用或未配置 baseUrl");
        }
        try {
            String plainJson = objectMapper.writeValueAsString(body);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.setAccept(java.util.Collections.singletonList(MediaType.APPLICATION_JSON));

            String requestBody = plainJson;
            boolean useCrypto = useCrypto(serviceName);
            if (useCrypto && cryptoService.isActive()) {
                VppUnSmCryptoUtil.EncryptedPayload payload = cryptoService.prepareRequest(plainJson);
                requestBody = VppUnCipherPayloadHelper.wrapCipherPayload(payload.getCipherBase64(), objectMapper);
                headers.set("X-Sign", payload.getSignBase64());
            }
            if (withToken) {
                headers.set("token", tokenHolder.getToken());
            }

            String url = normalizeUrl(properties.getBaseUrl()) + "/" + serviceName;
            log.debug("租户 {} UN 请求 {} crypto={} token={}",
                    VppUnTenantContext.resolveTenantId(), url, useCrypto && cryptoService.isActive(), withToken);

            ResponseEntity<String> response = restTemplate.exchange(
                    url, HttpMethod.POST, new HttpEntity<>(requestBody, headers), String.class);
            return decodeResponseBody(response.getBody(), response.getHeaders().getFirst("X-Sign"), useCrypto);
        } catch (HttpStatusCodeException ex) {
            String message = ex.getResponseBodyAsString();
            log.warn("UN 请求 {} 失败 status={} body={}", serviceName, ex.getRawStatusCode(), message);
            if (withToken && isTokenErrorMessage(message)) {
                tokenHolder.invalidate();
            }
            throw new BusinessException("运管平台请求失败: " + serviceName + " " + ex.getRawStatusCode()
                    + (StringUtils.hasText(message) ? " " + message : ""));
        } catch (ResourceAccessException ex) {
            throw new BusinessException("运管平台连接失败: " + ex.getMessage());
        } catch (BusinessException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new BusinessException("运管平台请求异常: " + ex.getMessage());
        }
    }

    private boolean isTokenError(BusinessException ex) {
        return isTokenErrorMessage(ex.getMessage());
    }

    private boolean isTokenErrorMessage(String message) {
        return message != null && (message.contains("TokenExpiredException")
                || message.contains("JWTVerificationException")
                || message.contains("NoToken")
                || message.contains("JWTDecodeException"));
    }

    private String decodeResponseBody(String body, String signHeader, boolean useCrypto) {
        if (!StringUtils.hasText(body)) {
            return "{}";
        }
        String trimmed = body.trim();
        if (!useCrypto || !cryptoService.isActive()) {
            return trimmed;
        }
        if (trimmed.startsWith("{")) {
            log.warn("UN 响应为明文 JSON，跳过验签解密");
            return trimmed;
        }
        String cipherBase64 = VppUnCipherPayloadHelper.extractCipherPayload(trimmed, objectMapper);
        return cryptoService.parseResponse(cipherBase64, signHeader);
    }

    private static boolean useCrypto(String serviceName) {
        return !TOKEN_REQUEST.equals(serviceName);
    }

    private static String normalizeUrl(String baseUrl) {
        if (baseUrl.endsWith("/")) {
            return baseUrl.substring(0, baseUrl.length() - 1);
        }
        return baseUrl;
    }
}
