package com.kingdee.eas.custom.integration.apimgt.auth.interceptor;

import com.kingdee.eas.custom.integration.apimgt.ApiRegistryBillInfo;
import com.kingdee.eas.custom.integration.apimgt.OpenPlatformAuthBillInfo;
import com.kingdee.eas.custom.integration.apimgt.auth.model.AuthErrorCode;
import com.kingdee.eas.custom.integration.apimgt.auth.model.AuthResult;
import com.kingdee.eas.custom.integration.apimgt.auth.service.SecretAuthService;
import com.kingdee.eas.custom.integration.apimgt.constant.ApiConstants;
import com.kingdee.eas.custom.integration.apimgt.context.OpenApiRequestContext;
import com.kingdee.eas.custom.integration.apimgt.log.ApiCallLogService;
import com.kingdee.eas.custom.integration.base.annotation.SkipAuth;
import com.kingdee.eas.custom.integration.base.annotation.SkipCallLog;
import com.kingdee.eas.custom.integration.base.dto.ApiResult;
import com.kingdee.eas.custom.integration.base.util.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

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

/**
 * OpenAPI 认证拦截器，基于密钥（appSecret）进行身份认证和权限校验，并记录调用日志。
 *
 * <p>处理流程：</p>
 * <ol>
 *   <li>只拦截 integration 包下的 Controller，其余直接放行</li>
 *   <li>缓存请求信息（路径、方法、URL、IP、起始时间），供 afterCompletion 写日志</li>
 *   <li>认证总开关，关闭时直接放行（仍记录日志，callerKey 标记为 auth_disabled）</li>
 *   <li>检查 @SkipAuth 注解，标记后跳过认证（callerKey 标记为 anonymous）</li>
 *   <li>检查 @SkipCallLog 注解，标记后跳过调用日志（存入 request attribute）</li>
 *   <li>根据接口路径和方法查询 ApiRegistryBill</li>
 *   <li>接口 isAuthRequired = false 则直接放行（callerKey 标记为 public）</li>
 *   <li>读取 X-Api-Secret 请求头</li>
 *   <li>校验密钥有效性和接口权限</li>
 *   <li>认证通过后写入 OpenApiRequestContext，供后续业务代码使用</li>
 * </ol>
 *
 * <p>调用日志记录：afterCompletion 中统一写入 ApiCallLog。</p>
 * <p>请求入参从 ContentCachingRequestWrapper 读取（需配合 ContentCachingFilter）。</p>
 * <p>响应体从 ContentCachingResponseWrapper 读取，鉴权失败时兜底取缓存的错误 JSON。</p>
 *
 * <p>跳过日志：满足以下任一条件跳过调用日志记录：</p>
 * <ul>
 *   <li>非 integration 包下的 Controller</li>
 *   <li>标记了 @SkipCallLog 注解的接口</li>
 * </ul>
 *
 * <p>异常处理：preHandle 内部 catch 所有异常，返回 JSON 错误响应而非抛出，
 * 避免穿透到 Servlet 容器返回 Tomcat HTML 错误页。</p>
 */
public class OpenApiAuthInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(OpenApiAuthInterceptor.class);
    /** 接口地址缓存 key */
    private static final String REQUEST_ATTR_API_PATH = "OPEN_API_API_PATH";
    /** HTTP 方法缓存 key */
    private static final String REQUEST_ATTR_HTTP_METHOD = "OPEN_API_HTTP_METHOD";
    /** 请求 URL 缓存 key */
    private static final String REQUEST_ATTR_REQUEST_URL = "OPEN_API_REQUEST_URL";
    /** 请求 IP 缓存 key */
    private static final String REQUEST_ATTR_REQUEST_IP = "OPEN_API_REQUEST_IP";
    /** 是否有错误标记 key */
    private static final String REQUEST_ATTR_HAS_ERROR = "OPEN_API_HAS_ERROR";
    /** 鉴权失败时的错误响应体缓存 key */
    private static final String REQUEST_ATTR_ERROR_BODY = "OPEN_API_ERROR_BODY";
    /** 是否跳过调用日志 key（isSkipCallLog） */
    private static final String REQUEST_ATTR_IS_SKIP_CALL_LOG = "OPEN_API_IS_SKIP_CALL_LOG";

    /** 密钥认证服务 */
    private final SecretAuthService secretAuthService;

    /** 调用日志服务 */
    private final ApiCallLogService callLogService;

    /** 认证总开关 */
    private boolean authEnabled = true;

    public OpenApiAuthInterceptor(SecretAuthService secretAuthService) {
        this.secretAuthService = secretAuthService;
        this.callLogService = new ApiCallLogService();
    }

    /**
     * 判断当前请求是否属于 integration 包下的 Controller。
     */
    private boolean isIntegrationController(Object handler) {
        if (!(handler instanceof HandlerMethod)) {
            return false;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        return handlerMethod.getBeanType().getName()
                .startsWith("com.kingdee.eas.custom.integration");
    }

    /**
     * 前置处理：缓存请求信息、执行认证和跳过日志标记流程。
     * 内部 catch 所有异常，避免穿透到 Servlet 容器返回 HTML 错误页。
     */
    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler) throws Exception {
        try {
            logger.debug("OpenApiAuthInterceptor preHandle 开始拦截处理");
            // 只拦截 integration 包下的 Controller
            if (!isIntegrationController(handler)) {
                return true;
            }
            logger.debug("OpenApiAuthInterceptor API 开始认证处理");
            // 记录请求起始时间
            request.setAttribute(ApiConstants.REQUEST_ATTR_START_TIME, System.currentTimeMillis());
            // 缓存请求信息，供 afterCompletion 写日志
            request.setAttribute(REQUEST_ATTR_API_PATH, request.getRequestURI());
            request.setAttribute(REQUEST_ATTR_HTTP_METHOD, request.getMethod());
            request.setAttribute(REQUEST_ATTR_REQUEST_URL, request.getRequestURL()
                    + (request.getQueryString() != null ? "?" + request.getQueryString() : ""));
            request.setAttribute(REQUEST_ATTR_REQUEST_IP, getClientIp(request));

            // 标记是否需要跳过调用日志
            if (isSkipCallLog(handler)) {
                request.setAttribute(REQUEST_ATTR_IS_SKIP_CALL_LOG, true);
            }

            if (!authEnabled) {
                logger.debug("认证总开关已关闭");
                request.setAttribute(ApiConstants.REQUEST_ATTR_CALLER_KEY, "auth_disabled");
                return true;
            }

            if (isSkipAuth(handler)) {
                // 跳过认证
                logger.debug("跳过认证");
                // 标记为匿名用户
                request.setAttribute(ApiConstants.REQUEST_ATTR_CALLER_KEY, "anonymous");
                return true;
            }

            // 获取接口路径和方法
            String apiPath = request.getRequestURI();
            String httpMethod = request.getMethod();

            // 根据接口路径和方法查询接口信息
            ApiRegistryBillInfo apiInfo = secretAuthService.findApi(apiPath, httpMethod);
            if (apiInfo == null) {
                logger.warn("接口未注册: {} {}", httpMethod, apiPath);
                request.setAttribute(REQUEST_ATTR_HAS_ERROR, true);
                writeErrorResponse(request, response, AuthErrorCode.API_NOT_FOUND);
                return false;
            }
            // 免授权接口直接放行
            if (!apiInfo.isIsAuthRequired()) {
                logger.debug("接口免授权: {}", apiPath);
                request.setAttribute(ApiConstants.REQUEST_ATTR_CALLER_KEY, "public");
                request.setAttribute(ApiConstants.REQUEST_ATTR_API_CODE, apiPath);
                return true;
            }

            // 获取密钥
            String appSecret = request.getHeader(ApiConstants.API_SECRET_HEADER);
            if (!StringUtils.hasText(appSecret)) {
                logger.warn("密钥缺失: {}", apiPath);
                request.setAttribute(REQUEST_ATTR_HAS_ERROR, true);
                writeErrorResponse(request, response, AuthErrorCode.SECRET_MISSING);
                return false;
            }
            // 认证
            AuthResult result = secretAuthService.authenticate(appSecret, apiInfo);
            if (!result.isSuccess()) {
                logger.warn("认证失败: {} {}", result.getErrorCode(), result.getErrorMessage());
                request.setAttribute(REQUEST_ATTR_HAS_ERROR, true);
                writeErrorResponse(request, response, result.getErrorCode(), result.getErrorMessage());
                return false;
            }
            // 认证通过，绑定上下文
            OpenPlatformAuthBillInfo platform = result.getPlatform();
            request.setAttribute(ApiConstants.REQUEST_ATTR_APP, platform);
            request.setAttribute(ApiConstants.REQUEST_ATTR_API_CODE, apiPath);
            request.setAttribute(ApiConstants.REQUEST_ATTR_CALLER_KEY, platform.getPlatform());
            // 写入线程上下文
            OpenApiRequestContext.bind(platform, apiInfo);

            return true;
        } catch (Exception e) {
            logger.error("preHandle 异常: {}", e.getMessage(), e);
            request.setAttribute(REQUEST_ATTR_HAS_ERROR, true);
            writeErrorResponse(request, response,
                    ApiResult.CODE_ERROR, "系统内部错误: " + e.getMessage());
            return false;
        }
    }

    /**
     * 请求完成后记录调用日志并清理上下文。
     * 非 integration 包或标记了 @SkipCallLog 的请求直接跳过，不记录日志。
     * 请求入参和响应体从 ContentCachingWrapper 读取。
     */
    @Override
    public void afterCompletion(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler, Exception ex) {
        // 非 integration 包下的请求不记日志
        if (!isIntegrationController(handler)) {
            return;
        }
        // 标记了 @SkipCallLog 的请求不记日志
        if (Boolean.TRUE.equals(request.getAttribute(REQUEST_ATTR_IS_SKIP_CALL_LOG))) {
            return;
        }
        // 获取缓存信息
        String apiPath = (String) request.getAttribute(REQUEST_ATTR_API_PATH);
        String httpMethod = (String) request.getAttribute(REQUEST_ATTR_HTTP_METHOD);
        String requestUrl = (String) request.getAttribute(REQUEST_ATTR_REQUEST_URL);
        String requestIp = (String) request.getAttribute(REQUEST_ATTR_REQUEST_IP);
        Long startTime = (Long) request.getAttribute(ApiConstants.REQUEST_ATTR_START_TIME);

        // 调用结果
        String callerKey = (String) request.getAttribute(ApiConstants.REQUEST_ATTR_CALLER_KEY);
        int httpStatus = response.getStatus();
        boolean hasError = Boolean.TRUE.equals(request.getAttribute(REQUEST_ATTR_HAS_ERROR));

        // 请求入参：从 ContentCachingRequestWrapper 读取 POST body，GET 用 queryString
        String requestParams = request.getQueryString();
        if (request instanceof ContentCachingRequestWrapper) {
            byte[] buf = ((ContentCachingRequestWrapper) request).getContentAsByteArray();
            if (buf.length > 0) {
                requestParams = new String(buf, StandardCharsets.UTF_8);
            }
        }
        // 响应体：ContentCachingResponseWrapper 缓存 → 鉴权错误 JSON → 异常信息
        String responseBody = null;
        if (response instanceof ContentCachingResponseWrapper) {
            byte[] buf = ((ContentCachingResponseWrapper) response).getContentAsByteArray();
            if (buf.length > 0) {
                responseBody = new String(buf, StandardCharsets.UTF_8);
            }
        }
        if (responseBody == null) {
            responseBody = (String) request.getAttribute(REQUEST_ATTR_ERROR_BODY);
        }
        if (responseBody == null && ex != null) {
            responseBody = "{\"code\":500,\"message\":\"" + ex.getMessage() + "\"}";
        }

        // 计算耗时
        long costTime = startTime != null ? System.currentTimeMillis() - startTime : 0;

        // 写入调用日志
        callLogService.record(
                callerKey,
                apiPath,
                httpMethod,
                requestUrl,
                requestParams,
                requestIp,
                responseBody,
                httpStatus,
                !hasError && ex == null,
                ex != null ? ex.getMessage() : null,
                costTime
        );

        // 清理线程上下文
        OpenApiRequestContext.clear();
    }

    /**
     * 获取接口路径（去除 contextPath）。
     */
    private String extractApiPath(HttpServletRequest request) {
        String uri = request.getRequestURI();
        String contextPath = request.getContextPath();
        if (contextPath != null && !contextPath.isEmpty() && uri.startsWith(contextPath)) {
            uri = uri.substring(contextPath.length());
        }
        return uri;
    }

    /**
     * 是否跳过认证（方法或类上有 @SkipAuth 注解）。
     */
    private boolean isSkipAuth(Object handler) {
        if (!(handler instanceof HandlerMethod)) {
            return false;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;

        SkipAuth methodAnnotation = handlerMethod.getMethodAnnotation(SkipAuth.class);
        if (methodAnnotation != null) {
            return true;
        }
        SkipAuth classAnnotation = handlerMethod.getBeanType().getAnnotation(SkipAuth.class);
        return classAnnotation != null;
    }

    /**
     * 是否跳过调用日志记录（方法或类上有 @SkipCallLog 注解）。
     */
    private boolean isSkipCallLog(Object handler) {
        if (!(handler instanceof HandlerMethod)) {
            return false;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;

        SkipCallLog methodAnnotation = handlerMethod.getMethodAnnotation(SkipCallLog.class);
        if (methodAnnotation != null) {
            return true;
        }
        SkipCallLog classAnnotation = handlerMethod.getBeanType().getAnnotation(SkipCallLog.class);
        return classAnnotation != null;
    }

    /**
     * 写入错误响应（JSON 格式，使用统一 ApiResult 结构），同时缓存到 request
     * 属性中供 afterCompletion 写入调用日志。
     */
    private void writeErrorResponse(HttpServletRequest request, HttpServletResponse response,
                                     AuthErrorCode errorCode) throws IOException {
        writeErrorResponse(request, response, errorCode.getCode(), errorCode.getMessage());
    }

    /**
     * 写入错误响应（无 request 参数的重载，不缓存错误体）。
     */
    private void writeErrorResponse(HttpServletResponse response, AuthErrorCode errorCode) throws IOException {
        writeErrorResponse(null, response, errorCode.getCode(), errorCode.getMessage());
    }

    private void writeErrorResponse(HttpServletRequest request, HttpServletResponse response,
                                     int errorCode, String errorMessage) throws IOException {
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType("application/json;charset=UTF-8");
        ApiResult<Void> body = ApiResult.fail(errorCode, errorMessage);
        String json = JsonUtils.toJson(body);
        // 缓存错误响应体，供 afterCompletion 写入日志
        if (request != null) {
            request.setAttribute(REQUEST_ATTR_ERROR_BODY, json);
        }
        response.getWriter().write(json);
    }

    /**
     * 获取客户端 IP，优先从 X-Forwarded-For 头获取。
     */
    private String getClientIp(HttpServletRequest request) {
        String forwarded = request.getHeader("X-Forwarded-For");
        if (forwarded != null && !forwarded.isEmpty()) {
            return forwarded.split(",")[0].trim();
        }
        String realIp = request.getHeader("X-Real-IP");
        if (realIp != null && !realIp.isEmpty()) {
            return realIp;
        }
        return request.getRemoteAddr();
    }

    /**
     * 设置认证总开关。
     */
    public void setAuthEnabled(boolean authEnabled) {
        this.authEnabled = authEnabled;
    }
}
