package cn.iocoder.yudao.module.system.controller.admin;

import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.controller.admin.vo.InternalProxyReqVO;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.validation.Valid;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;

/**
 * 内部服务调用代理控制器
 * 用于服务间调用，无需登录验证，避免每次都在配置文件中添加白名单
 *
 * @author system
 */
@Tag(name = "管理后台 - 内部服务调用代理")
@RestController
@RequestMapping("/internal")
@Validated
@Slf4j
public class InternalProxyController {

    @Resource
    private ObjectMapper objectMapper;

    /**
     * 通用内部服务调用代理接口
     * 通过此接口可以调用其他需要登录验证的接口，而无需配置白名单
     *
     * @param reqVO 代理请求参数
     * @param request 原始HTTP请求
     * @param response HTTP响应
     * @return 目标接口的响应结果
     */
    @PostMapping("/proxy")
    @Operation(summary = "内部服务调用代理接口", description = "用于服务间调用，无需登录验证")
    @PermitAll
    public CommonResult<Object> proxy(@Valid @RequestBody InternalProxyReqVO reqVO,
                                       HttpServletRequest request,
                                       HttpServletResponse response) {
        try {
            // 构建目标路径
            String targetPath = reqVO.getTargetPath();
            if (!targetPath.startsWith("/")) {
                targetPath = "/" + targetPath;
            }
            // 确保路径以 /admin-api 开头
            if (!targetPath.startsWith("/admin-api")) {
                targetPath = "/admin-api" + targetPath;
            }

            // 创建包装的请求对象
            ProxyHttpServletRequest proxyRequest = new ProxyHttpServletRequest(
                    request, targetPath, reqVO.getHttpMethod(), reqVO.getRequestBody(),
                    reqVO.getQueryParams(), reqVO.getHeaders(), objectMapper);

            // 创建包装的响应对象
            ProxyHttpServletResponse proxyResponse = new ProxyHttpServletResponse(response);

            // 使用RequestDispatcher转发请求
            javax.servlet.RequestDispatcher dispatcher = request.getRequestDispatcher(targetPath);
            if (dispatcher != null) {
                dispatcher.forward(proxyRequest, proxyResponse);
            } else {
                throw new RuntimeException("无法找到目标路径: " + targetPath);
            }

            // 获取响应内容
            String responseBody = proxyResponse.getResponseBody();
            if (StrUtil.isNotBlank(responseBody)) {
                // 尝试解析为JSON对象
                try {
                    Object jsonObject = objectMapper.readValue(responseBody, Object.class);
                    return success(jsonObject);
                } catch (Exception e) {
                    // 如果不是JSON，直接返回字符串
                    return success(responseBody);
                }
            }

            return success(null);
        } catch (Exception e) {
            log.error("内部服务调用代理失败，目标路径: {}, 错误信息: {}", reqVO.getTargetPath(), e.getMessage(), e);
            return CommonResult.error(500, "内部服务调用失败: " + e.getMessage());
        }
    }

    /**
     * 代理HTTP请求包装类
     */
    private static class ProxyHttpServletRequest extends HttpServletRequestWrapper {
        private final String targetPath;
        private final String method;
        private final Object requestBody;
        private final Map<String, String> queryParams;
        private final Map<String, String> headers;
        private final ObjectMapper objectMapper;
        private String bodyContent;
        private byte[] bodyBytes;

        public ProxyHttpServletRequest(HttpServletRequest request, String targetPath, String method,
                                       Object requestBody, Map<String, String> queryParams,
                                       Map<String, String> headers, ObjectMapper objectMapper) {
            super(request);
            this.targetPath = targetPath;
            this.method = method != null ? method.toUpperCase() : "POST";
            this.requestBody = requestBody;
            this.queryParams = queryParams != null ? queryParams : new java.util.HashMap<>();
            this.headers = headers != null ? headers : new java.util.HashMap<>();
            this.objectMapper = objectMapper;

            try {
                if (requestBody != null) {
                    this.bodyContent = objectMapper.writeValueAsString(requestBody);
                    this.bodyBytes = bodyContent.getBytes(StandardCharsets.UTF_8);
                }
            } catch (Exception e) {
                this.bodyContent = requestBody != null ? requestBody.toString() : "";
                this.bodyBytes = bodyContent.getBytes(StandardCharsets.UTF_8);
            }
        }

        @Override
        public String getRequestURI() {
            return targetPath;
        }

        @Override
        public String getQueryString() {
            if (queryParams == null || queryParams.isEmpty()) {
                return null;
            }
            StringBuilder queryString = new StringBuilder();
            try {
                for (Map.Entry<String, String> entry : queryParams.entrySet()) {
                    if (queryString.length() > 0) {
                        queryString.append("&");
                    }
                    String key = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name());
                    String value = entry.getValue() != null 
                            ? URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name()) 
                            : "";
                    queryString.append(key).append("=").append(value);
                }
            } catch (UnsupportedEncodingException e) {
                // UTF-8 应该总是支持的，这里不会抛出异常
                // 如果确实抛出异常，返回null
                return null;
            }
            return queryString.length() > 0 ? queryString.toString() : null;
        }

        @Override
        public String getMethod() {
            return method;
        }

        @Override
        public String getContentType() {
            return MediaType.APPLICATION_JSON_VALUE;
        }

        @Override
        public int getContentLength() {
            return bodyBytes != null ? bodyBytes.length : 0;
        }

        @Override
        public long getContentLengthLong() {
            return bodyBytes != null ? bodyBytes.length : 0;
        }

        @Override
        public String getHeader(String name) {
            if (headers.containsKey(name)) {
                return headers.get(name);
            }
            if ("Content-Type".equalsIgnoreCase(name)) {
                return MediaType.APPLICATION_JSON_VALUE;
            }
            return super.getHeader(name);
        }

        @Override
        public String getParameter(String name) {
            if (queryParams.containsKey(name)) {
                return queryParams.get(name);
            }
            return super.getParameter(name);
        }

        @Override
        public Map<String, String[]> getParameterMap() {
            Map<String, String[]> paramMap = new HashMap<>(super.getParameterMap());
            for (Map.Entry<String, String> entry : queryParams.entrySet()) {
                paramMap.put(entry.getKey(), new String[]{entry.getValue()});
            }
            return paramMap;
        }

        @Override
        public Enumeration<String> getParameterNames() {
            Set<String> paramNames = new HashSet<>(queryParams.keySet());
            Enumeration<String> originalNames = super.getParameterNames();
            while (originalNames.hasMoreElements()) {
                paramNames.add(originalNames.nextElement());
            }
            return Collections.enumeration(paramNames);
        }

        @Override
        public String[] getParameterValues(String name) {
            if (queryParams.containsKey(name)) {
                return new String[]{queryParams.get(name)};
            }
            return super.getParameterValues(name);
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            if (bodyBytes != null) {
                return new ProxyServletInputStream(new ByteArrayInputStream(bodyBytes));
            }
            return super.getInputStream();
        }

        @Override
        public BufferedReader getReader() throws IOException {
            if (bodyContent != null) {
                return new BufferedReader(new StringReader(bodyContent));
            }
            return super.getReader();
        }

        private static class ProxyServletInputStream extends ServletInputStream {
            private final InputStream inputStream;

            public ProxyServletInputStream(InputStream inputStream) {
                this.inputStream = inputStream;
            }

            @Override
            public int read() throws IOException {
                return inputStream.read();
            }

            @Override
            public boolean isFinished() {
                try {
                    return inputStream.available() == 0;
                } catch (IOException e) {
                    return true;
                }
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
                // 不支持异步读取
            }
        }
    }

    /**
     * 代理HTTP响应包装类
     */
    private static class ProxyHttpServletResponse extends HttpServletResponseWrapper {
        private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        private final PrintWriter printWriter = new PrintWriter(
                new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));

        public ProxyHttpServletResponse(HttpServletResponse response) {
            super(response);
        }

        public String getResponseBody() throws UnsupportedEncodingException {
            printWriter.flush();
            return outputStream.toString(String.valueOf(StandardCharsets.UTF_8));
        }

        @Override
        public PrintWriter getWriter() {
            return printWriter;
        }

        @Override
        public ServletOutputStream getOutputStream() {
            return new ProxyServletOutputStream(outputStream);
        }

        private static class ProxyServletOutputStream extends ServletOutputStream {
            private final ByteArrayOutputStream outputStream;

            public ProxyServletOutputStream(ByteArrayOutputStream outputStream) {
                this.outputStream = outputStream;
            }

            @Override
            public void write(int b) {
                outputStream.write(b);
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setWriteListener(WriteListener writeListener) {
                // 不支持异步写入
            }
        }
    }
}

