package tech.easyflow.common.web.error;

import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.ModelAndView;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.math.BigInteger;

import static org.junit.Assert.assertEquals;

/**
 * {@link GlobalErrorResolver} HTTP 状态与安全错误体回归测试。
 */
public class GlobalErrorResolverTest {

    private final GlobalErrorResolver resolver = new GlobalErrorResolver();

    /**
     * 验证业务冲突不会被包装为 HTTP 200。
     */
    @Test
    public void shouldPreserveBusinessHttpStatusAndErrorCode() {
        Resolution resolution = resolve(new BusinessException(409, 4091, "文件版本冲突"));

        assertEquals(409, resolution.response.getStatus());
        assertEquals(4091, resolution.modelAndView.getModel().get("errorCode"));
        assertEquals("文件版本冲突", resolution.modelAndView.getModel().get("message"));
    }

    /**
     * 验证 Spring 标准状态异常保留 4xx 语义和安全原因。
     */
    @Test
    public void shouldPreserveResponseStatusException() {
        Resolution resolution = resolve(new ResponseStatusException(HttpStatus.NOT_FOUND, "Skill 不存在"));

        assertEquals(404, resolution.response.getStatus());
        assertEquals(404, resolution.modelAndView.getModel().get("errorCode"));
        assertEquals("Skill 不存在", resolution.modelAndView.getModel().get("message"));
    }

    /**
     * 验证框架级 5xx 异常不会向客户端泄露 reason。
     */
    @Test
    public void shouldHideResponseStatusServerErrorReason() {
        Resolution resolution = resolve(new ResponseStatusException(
                HttpStatus.INTERNAL_SERVER_ERROR, "database-secret-detail"));

        assertEquals(500, resolution.response.getStatus());
        assertEquals(500, resolution.modelAndView.getModel().get("errorCode"));
        assertEquals("服务暂时不可用，请稍后重试", resolution.modelAndView.getModel().get("message"));
    }

    /**
     * 验证注解声明的 HTTP 状态不会被统一异常处理覆盖。
     */
    @Test
    public void shouldPreserveAnnotatedResponseStatus() {
        Resolution resolution = resolve(new AnnotatedConflictException());

        assertEquals(409, resolution.response.getStatus());
        assertEquals("状态冲突", resolution.modelAndView.getModel().get("message"));
    }

    /**
     * 验证未知异常返回真实 500，且不会向客户端泄露内部异常信息。
     */
    @Test
    public void shouldHideUnexpectedExceptionDetails() {
        Resolution resolution = resolve(new IllegalStateException("database-secret-detail"));

        assertEquals(500, resolution.response.getStatus());
        assertEquals(500, resolution.modelAndView.getModel().get("errorCode"));
        assertEquals("服务暂时不可用，请稍后重试", resolution.modelAndView.getModel().get("message"));
    }

    /**
     * 验证请求参数类型错误返回安全的 HTTP 400。
     *
     * @throws Exception 构造反射参数失败
     */
    @Test
    public void shouldReturnSafeBadRequestForTypeMismatch() throws Exception {
        MethodParameter parameter = new MethodParameter(
                GlobalErrorResolverTest.class.getDeclaredMethod("sampleParameter", BigInteger.class), 0);
        MethodArgumentTypeMismatchException exception = new MethodArgumentTypeMismatchException(
                "not-an-id", BigInteger.class, "id", parameter,
                new NumberFormatException("sensitive-converter-detail"));

        Resolution resolution = resolve(exception);

        assertEquals(400, resolution.response.getStatus());
        assertEquals(400, resolution.modelAndView.getModel().get("errorCode"));
        assertEquals("请求参数格式不正确", resolution.modelAndView.getModel().get("message"));
    }

    /**
     * 提供反射参数签名。
     *
     * @param id 示例 ID
     */
    private static void sampleParameter(BigInteger id) {
        // 仅用于构造 Spring MethodParameter 测试数据。
    }

    private Resolution resolve(Exception exception) {
        MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/skill/file/save");
        MockHttpServletResponse response = new MockHttpServletResponse();
        ModelAndView modelAndView = resolver.resolveException(request, response, this, exception);
        return new Resolution(response, modelAndView);
    }

    @ResponseStatus(code = HttpStatus.CONFLICT, reason = "状态冲突")
    private static final class AnnotatedConflictException extends RuntimeException {
        private static final long serialVersionUID = 1L;
    }

    private record Resolution(MockHttpServletResponse response, ModelAndView modelAndView) {
    }
}
