package com.seer.teach.openapi.config;

import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.json.JSONUtil;
import com.seer.teach.common.ResultBean;
import com.seer.teach.common.enums.ResultCodeEnum;
import com.seer.teach.common.utils.SignUtils;
import com.seer.teach.openapi.service.AppKeySecretsService;
import com.seer.teach.openapi.service.SnCodesService;
import com.seer.teach.openapi.uitl.StpOpenApiUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Component
@RequiredArgsConstructor
public class DecryptRequestFilter extends OncePerRequestFilter {

    private final AppKeySecretsService appKeySecretsService;

    private final SnCodesService snCodesService;

    private final List<String> includePaths = Arrays.asList("/seer/open/rest/business/**");

    private final List<String> excludePaths = Arrays.asList("/seer/open/rest/oauth/token", "/seer/open/rest/oauth/revoke", "/seer/open/rest/sign");

    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    /**
     * 匹配请求URI是否需要进行解密处理
     *
     * @param request HTTP请求对象
     * @return true表示需要解密处理，false表示跳过解密
     */
    private boolean match(HttpServletRequest request) {
        String uri = request.getRequestURI();
        // 检查排除路径
        for (String e : excludePaths) {
            if (pathMatcher.match(e, uri)) {
                return false;
            }
        }

        // 检查包含路径
        for (String i : includePaths) {
            if (pathMatcher.match(i, uri)) {
                return true;
            }
        }
        return false;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        if (!match(request) || !("POST".equalsIgnoreCase(request.getMethod()) || "PUT".equalsIgnoreCase(request.getMethod()))) {
            filterChain.doFilter(request, response);
            return;
        }

        // 包装请求，缓存 body
        CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(request);

        try {
            int snId = StpOpenApiUtils.getLoginIdAsInt();
            System.out.println("snId = " + snId);
            Integer appId = snCodesService.getById(snId).getAppId();
            String secret = appKeySecretsService.getById(appId).getSecret();

            String body = new String(cachedRequest.getCachedBody(), StandardCharsets.UTF_8);
            String decrypted = SignUtils.decrypt(body, secret);

            HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(cachedRequest) {
                @Override
                public ServletInputStream getInputStream() {
                    final ByteArrayInputStream bais = new ByteArrayInputStream(decrypted.getBytes(StandardCharsets.UTF_8));
                    return new ServletInputStream() {
                        @Override
                        public boolean isFinished() {
                            return bais.available() == 0;
                        }

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

                        @Override
                        public void setReadListener(ReadListener readListener) {
                        }

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

                @Override
                public BufferedReader getReader() throws IOException {
                    return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
                }
            };

            filterChain.doFilter(wrapper, response);
        } catch (NotLoginException e) {
            // 打印堆栈，以供调试
            if(e.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
                writeResultBean(response, ResultBean.error(ResultCodeEnum.EXPIRED_TOKEN));
                return;
            }
            writeResultBean(response, ResultBean.error(ResultCodeEnum.INVALID_TOKEN));
        } catch (Exception e) {
            e.printStackTrace();
            writeResultBean(response, ResultBean.error(ResultCodeEnum.DECRYPTED_FAIL));
        }

    }

    /**
     * 写ResultBean JSON到响应
     */
    private void writeResultBean(HttpServletResponse response, ResultBean<?> resultBean) throws IOException {
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(JSONUtil.toJsonStr(resultBean));
        response.getWriter().flush();
    }


    /**
     * 缓存请求体
     */
    public static class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {

        private final byte[] cachedBody;

        public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
            super(request);
            cachedBody = request.getReader().lines().collect(Collectors.joining("\n")).getBytes(StandardCharsets.UTF_8);
        }

        public byte[] getCachedBody() {
            return cachedBody;
        }

        @Override
        public ServletInputStream getInputStream() {
            final ByteArrayInputStream bais = new ByteArrayInputStream(cachedBody);
            return new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return bais.available() == 0;
                }

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

                @Override
                public void setReadListener(ReadListener readListener) {
                }

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

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
        }
    }
}
