package tech.easyflow.publicapi.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.system.entity.SysApiKey;
import tech.easyflow.system.service.SysApiKeyService;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Proxy;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * {@link PublicApiInterceptor} 鉴权响应测试。
 */
public class PublicApiInterceptorTest {

    /**
     * 验证缺少访问令牌时返回真实 HTTP 401 和统一错误体。
     *
     * @throws Exception 拦截器处理失败时抛出
     */
    @Test
    public void shouldReturnUnauthorizedWhenApiKeyMissing() throws Exception {
        StringWriter body = new StringWriter();
        AtomicInteger status = new AtomicInteger();
        HttpServletRequest request = proxy(
                HttpServletRequest.class,
                (instance, method, args) -> {
                    if ("getRequestURI".equals(method.getName())) {
                        return "/public-api/workflow/runAsync";
                    }
                    if ("getHeader".equals(method.getName())) {
                        return null;
                    }
                    if ("getAttribute".equals(method.getName())) {
                        return "request-1";
                    }
                    throw new AssertionError(
                            "测试路径不应调用 HttpServletRequest."
                                    + method.getName());
                });
        HttpServletResponse response = proxy(
                HttpServletResponse.class,
                (instance, method, args) -> {
                    if ("setStatus".equals(method.getName())) {
                        status.set((Integer) args[0]);
                        return null;
                    }
                    if ("setContentType".equals(method.getName())) {
                        return null;
                    }
                    if ("getWriter".equals(method.getName())) {
                        return new PrintWriter(body);
                    }
                    throw new AssertionError(
                            "测试路径不应调用 HttpServletResponse."
                                    + method.getName());
                });

        boolean allowed = new PublicApiInterceptor()
                .preHandle(request, response, new Object());

        Assert.assertFalse(allowed);
        Assert.assertEquals(
                HttpServletResponse.SC_UNAUTHORIZED,
                status.get());
        Assert.assertTrue(body.toString().contains("\"errorCode\":40101"));
        Assert.assertTrue(body.toString().contains("缺少 ApiKey 请求头"));
        Assert.assertTrue(body.toString().contains("request-1"));
    }

    /**
     * 验证非工作流公共 API 缺少令牌时继续保留既有通用错误契约。
     *
     * @throws Exception 拦截器处理失败时抛出
     */
    @Test
    public void shouldKeepGenericMissingKeyContractForOtherPublicApis()
            throws Exception {
        StringWriter body = new StringWriter();
        AtomicInteger status = new AtomicInteger();
        HttpServletRequest request = proxy(
                HttpServletRequest.class,
                (instance, method, args) -> {
                    if ("getRequestURI".equals(method.getName())) {
                        return "/public-api/knowledge-share/detail";
                    }
                    if ("getHeader".equals(method.getName())) {
                        return null;
                    }
                    throw new AssertionError(
                            "测试路径不应调用 HttpServletRequest."
                                    + method.getName());
                });
        HttpServletResponse response = proxy(
                HttpServletResponse.class,
                (instance, method, args) -> {
                    if ("setStatus".equals(method.getName())) {
                        status.set((Integer) args[0]);
                        return null;
                    }
                    if ("setContentType".equals(method.getName())) {
                        return null;
                    }
                    if ("getWriter".equals(method.getName())) {
                        return new PrintWriter(body);
                    }
                    throw new AssertionError(
                            "测试路径不应调用 HttpServletResponse."
                                    + method.getName());
                });

        boolean allowed = new PublicApiInterceptor()
                .preHandle(request, response, new Object());

        Assert.assertFalse(allowed);
        Assert.assertEquals(
                HttpServletResponse.SC_UNAUTHORIZED,
                status.get());
        Assert.assertTrue(body.toString().contains("\"errorCode\":401"));
        Assert.assertTrue(body.toString().contains("密钥不正确"));
        Assert.assertFalse(body.toString().contains("工作流 Public API Key"));
    }

    /**
     * 验证通过接口权限校验的访问令牌会写入请求，供资源级鉴权复用。
     *
     * @throws Exception 拦截器处理失败时抛出
     */
    @Test
    public void shouldExposeAuthenticatedApiKeyToController() throws Exception {
        SysApiKey authenticated = new SysApiKey();
        AtomicReference<Object> requestAttribute = new AtomicReference<>();
        HttpServletRequest request = proxy(
                HttpServletRequest.class,
                (instance, method, args) -> {
                    if ("getRequestURI".equals(method.getName())) {
                        return "/public-api/knowledge-share/detail";
                    }
                    if ("getHeader".equals(method.getName())) {
                        return "test-key";
                    }
                    if ("setAttribute".equals(method.getName())) {
                        Assert.assertEquals(
                                PublicApiInterceptor.AUTHENTICATED_API_KEY_ATTRIBUTE,
                                args[0]);
                        requestAttribute.set(args[1]);
                        return null;
                    }
                    throw new AssertionError(
                            "测试路径不应调用 HttpServletRequest."
                                    + method.getName());
                });
        HttpServletResponse response = proxy(
                HttpServletResponse.class,
                (instance, method, args) -> {
                    throw new AssertionError(
                            "测试路径不应调用 HttpServletResponse."
                                    + method.getName());
                });
        SysApiKeyService service = proxy(
                SysApiKeyService.class,
                (instance, method, args) -> {
                    if ("checkApikeyPermission".equals(method.getName())) {
                        return authenticated;
                    }
                    throw new AssertionError(
                            "测试路径不应调用 SysApiKeyService."
                                    + method.getName());
                });
        PublicApiInterceptor interceptor = new PublicApiInterceptor();
        Field serviceField = PublicApiInterceptor.class
                .getDeclaredField("sysApiKeyService");
        serviceField.setAccessible(true);
        serviceField.set(interceptor, service);

        boolean allowed =
                interceptor.preHandle(request, response, new Object());

        Assert.assertTrue(allowed);
        Assert.assertSame(authenticated, requestAttribute.get());
    }

    /**
     * 创建接口代理。
     *
     * @param type 接口类型
     * @param handler 调用处理器
     * @param <T> 接口类型
     * @return 代理实例
     */
    private <T> T proxy(
            Class<T> type,
            java.lang.reflect.InvocationHandler handler) {
        return type.cast(Proxy.newProxyInstance(
                type.getClassLoader(),
                new Class<?>[]{type},
                handler));
    }
}
