package cn.nianxx.thhotel.integrations.ai.superagent.service.impl;

import cn.nianxx.thhotel.integrations.ai.superagent.common.request.SuperAgentTaskResultSecurityRequest;
import cn.nianxx.thhotel.integrations.ai.superagent.repository.SuperAgentTaskResultNonceRepository;
import cn.nianxx.thhotel.integrations.ai.superagent.service.SuperAgentTaskResultSecurityService;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

/**
 * SuperAgent HMAC 签名校验实现。该服务不解析业务 JSON，避免鉴权前处理外部业务内容。
 */
@Service
public class SuperAgentTaskResultSecurityServiceImpl implements SuperAgentTaskResultSecurityService {

    private static final String SIGNATURE_PREFIX = "sha256=";
    private static final int CLIENT_ID_MAX_LENGTH = 128;
    private static final int TIMESTAMP_MAX_LENGTH = 64;
    private static final int NONCE_MAX_LENGTH = 256;
    private static final int SIGNATURE_MAX_LENGTH = 71;

    private final SuperAgentTaskResultProperties properties;
    private final SuperAgentTaskResultNonceRepository nonceRepository;
    private final Clock clock;

    /**
     * 注入配置、nonce 持久化边界和 UTC 时钟，便于后续测试时间窗口。
     */
    public SuperAgentTaskResultSecurityServiceImpl(
            SuperAgentTaskResultProperties properties,
            SuperAgentTaskResultNonceRepository nonceRepository) {
        this.properties = properties;
        this.nonceRepository = nonceRepository;
        this.clock = Clock.systemUTC();
    }

    /**
     * 按 Header 完整性、时间窗口、签名和 nonce 顺序校验请求；失败时只返回安全错误码。
     */
    @Override
    public void verify(SuperAgentTaskResultSecurityRequest request) {
        String clientId = requireHeader(request.clientId(), CLIENT_ID_MAX_LENGTH);
        String timestamp = requireHeader(request.timestamp(), TIMESTAMP_MAX_LENGTH);
        String nonce = requireHeader(request.nonce(), NONCE_MAX_LENGTH);
        String signature = requireHeader(request.signature(), SIGNATURE_MAX_LENGTH);
        String secret = trimToNull(properties.getHmacSecret());
        if (secret == null) {
            throw error(HttpStatus.UNAUTHORIZED, "AUTH_SIGNATURE_INVALID", "签名校验失败。");
        }

        Instant requestInstant = parseTimestamp(timestamp);
        long skew = Math.max(properties.getClockSkewSeconds(), 0L);
        if (Math.abs(Duration.between(Instant.now(clock), requestInstant).toSeconds()) > skew) {
            throw error(HttpStatus.UNAUTHORIZED, "AUTH_TIMESTAMP_INVALID", "请求时间无效或超出窗口。");
        }

        String expectedSignature = SIGNATURE_PREFIX + hmac(secret, canonicalString(request, timestamp, nonce, clientId));
        if (!constantTimeEquals(expectedSignature, signature)) {
            throw error(HttpStatus.UNAUTHORIZED, "AUTH_SIGNATURE_INVALID", "签名校验失败。");
        }

        LocalDateTime now = LocalDateTime.ofInstant(Instant.now(clock), ZoneOffset.UTC);
        LocalDateTime expiresAt = now.plusSeconds(Math.max(properties.getNonceTtlSeconds(), 1L));
        if (!nonceRepository.saveIfAbsent(clientId, nonce, expiresAt, now)) {
            throw error(HttpStatus.CONFLICT, "AUTH_NONCE_REPLAY", "Nonce 已被使用。");
        }
    }

    /**
     * 校验必填 Header 文本，避免空白调用方或 nonce 进入签名逻辑。
     */
    private String requireHeader(String value, int maxLength) {
        String trimmed = trimToNull(value);
        if (trimmed == null) {
            throw error(HttpStatus.UNAUTHORIZED, "AUTH_HEADER_MISSING", "鉴权 Header 缺失。");
        }
        if (trimmed.length() > maxLength) {
            throw error(HttpStatus.UNAUTHORIZED, "AUTH_HEADER_INVALID", "鉴权 Header 无效。");
        }
        return trimmed;
    }

    /**
     * 解析 ISO-8601 UTC 时间，格式错误统一返回时间非法。
     */
    private Instant parseTimestamp(String timestamp) {
        try {
            return Instant.parse(timestamp);
        } catch (RuntimeException exception) {
            throw error(HttpStatus.UNAUTHORIZED, "AUTH_TIMESTAMP_INVALID", "请求时间无效或超出窗口。");
        }
    }

    /**
     * 按契约拼接规范签名串，确保双方签名输入一致。
     */
    private String canonicalString(
            SuperAgentTaskResultSecurityRequest request,
            String timestamp,
            String nonce,
            String clientId) {
        return request.httpMethod() + "\n"
                + request.requestPath() + "\n"
                + timestamp + "\n"
                + nonce + "\n"
                + clientId + "\n"
                + sha256(request.rawBody() == null ? "" : request.rawBody());
    }

    /**
     * 对规范签名串计算 HMAC-SHA256。
     */
    private String hmac(String secret, String canonical) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            return HexFormat.of().formatHex(mac.doFinal(canonical.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception exception) {
            throw new IllegalStateException("当前 Java 运行时不支持 HmacSHA256", exception);
        }
    }

    /**
     * 计算原始请求体 SHA-256，签名只使用哈希，不把请求体写入日志。
     */
    private String sha256(String value) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            return HexFormat.of().formatHex(digest.digest(value.getBytes(StandardCharsets.UTF_8)));
        } catch (NoSuchAlgorithmException exception) {
            throw new IllegalStateException("当前 Java 运行时不支持 SHA-256", exception);
        }
    }

    /**
     * 常量时间比较签名，避免因字符串比较提前退出泄漏签名差异。
     */
    private boolean constantTimeEquals(String expected, String actual) {
        if (expected == null || actual == null) {
            return false;
        }
        return MessageDigest.isEqual(
                expected.getBytes(StandardCharsets.UTF_8),
                actual.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 构建安全受控异常，响应不包含外部请求原文。
     */
    private SuperAgentTaskResultException error(HttpStatus status, String errorCode, String message) {
        return new SuperAgentTaskResultException(status, errorCode, message);
    }

    /**
     * 将空白字符串统一为 null。
     */
    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }
}
