package com.malk.apigw.config;

import com.alibaba.fastjson.JSON;
import com.malk.apigw.caller.CallerProfile;
import com.malk.apigw.caller.CallerRateLimiter;
import com.malk.apigw.caller.CallerRegistryService;
import com.malk.core.NonceCache;
import com.malk.server.common.McR;
import com.malk.utils.UtilSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;

/**
 * 调用方鉴权拦截器
 *
 * <p>复用基座 {@link UtilSignature} + {@link NonceCache}；按 callerId 查 secret + 限流。</p>
 *
 * <p>校验顺序（capability: baas-gateway / REQ-GW-002）：</p>
 * <ol>
 *   <li>Header 齐全（X-Caller-Id / X-MJ-Timestamp / X-MJ-Nonce / X-MJ-Signature）</li>
 *   <li>时间窗 5 分钟</li>
 *   <li>callerId 查注册表 + enabled + 未过期</li>
 *   <li>Nonce 去重</li>
 *   <li>HMAC 签名比对</li>
 *   <li>限流</li>
 * </ol>
 */
@Component
public class CallerAuthInterceptor extends HandlerInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger("point");
    public static final String H_CALLER = "X-Caller-Id";
    public static final String H_TS = "X-MJ-Timestamp";
    public static final String H_NONCE = "X-MJ-Nonce";
    public static final String H_SIG = "X-MJ-Signature";
    public static final String ATTR_CALLER = "com.malk.apigw.caller";

    private static final long WINDOW_MS = 300_000L;

    @Autowired
    private CallerRegistryService registry;

    @Autowired
    private NonceCache nonceCache;

    @Autowired
    private CallerRateLimiter rateLimiter;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String callerId = request.getHeader(H_CALLER);
        String ts = request.getHeader(H_TS);
        String nonce = request.getHeader(H_NONCE);
        String sig = request.getHeader(H_SIG);
        if (empty(callerId) || empty(ts) || empty(nonce) || empty(sig)) {
            return write(response, 401, "AUTH_FAILED");
        }

        long clientMs;
        try {
            clientMs = Long.parseLong(ts);
        } catch (NumberFormatException e) {
            return write(response, 401, "AUTH_FAILED");
        }
        if (Math.abs(System.currentTimeMillis() - clientMs) > WINDOW_MS) {
            return write(response, 401, "AUTH_FAILED");
        }

        CallerProfile caller = registry.get(callerId);
        if (caller == null || !caller.isEnabled()) {
            log.warn("[CallerAuth] disabled or not found callerId={}", callerId);
            return write(response, 401, "AUTH_FAILED");
        }
        if (caller.getExpireAt() != null && caller.getExpireAt().getTime() < System.currentTimeMillis()) {
            return write(response, 401, "AUTH_FAILED");
        }

        if (!nonceCache.putIfAbsent(nonce)) {
            return write(response, 401, "AUTH_NONCE_REPLAYED");
        }

        String body = readBody(request);
        String bodyHash = UtilSignature.sha256Hex(body.getBytes(StandardCharsets.UTF_8));
        String expected = UtilSignature.sign(caller.getCallerSecret(), ts, nonce,
                request.getMethod().toUpperCase(), request.getServletPath(), bodyHash);
        if (!UtilSignature.safeEquals(sig, expected)) {
            log.warn("[CallerAuth] signature mismatch callerId={} path={}", callerId, request.getServletPath());
            return write(response, 403, "AUTH_SIGNATURE_INVALID");
        }

        if (!rateLimiter.tryAcquire(callerId, caller.getRateLimit())) {
            return write(response, 429, "RATE_LIMITED");
        }

        request.setAttribute(ATTR_CALLER, caller);
        MDC.put("callerId", callerId);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        MDC.remove("callerId");
    }

    private static boolean empty(String s) {
        return s == null || s.isEmpty();
    }

    private static String readBody(HttpServletRequest req) throws java.io.IOException {
        BufferedReader reader = req.getReader();
        if (reader == null) return "";
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) sb.append(line);
        return sb.toString();
    }

    private static boolean write(HttpServletResponse resp, int status, String code) throws java.io.IOException {
        resp.setStatus(status);
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json;charset=UTF-8");
        McR r = McR.error(code, code);
        resp.getOutputStream().write(JSON.toJSONString(r).getBytes(StandardCharsets.UTF_8));
        return false;
    }
}
