package cn.lihongjie.coal.filter;

import cn.lihongjie.coal.base.dto.R;
import cn.lihongjie.coal.common.Constants;
import cn.lihongjie.coal.common.Ctx;
import cn.lihongjie.coal.common.RequestUtils;
import cn.lihongjie.coal.exception.BizException;
import cn.lihongjie.coal.ip.IpQueryService;
import cn.lihongjie.coal.loginUser.service.LoginUserService;
import cn.lihongjie.coal.ratelimit.RateLimiterService;
import cn.lihongjie.coal.resource.dto.ResourceDto;
import cn.lihongjie.coal.sysconfig.service.SysConfigService;
import cn.lihongjie.coal.syslog.service.SysLogService;

import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.ContentType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
@Order(200)
@Slf4j
public class RateLimitFilter extends OncePerRequestFilter {

    @Autowired SysConfigService sysConfigService;
    @Autowired ObjectMapper objectMapper;
    @Autowired RedissonClient redissonClient;

    @Autowired SysLogService sysLogService;
    @Autowired IpQueryService ipQueryService;
    @Autowired LoginUserService loginUserService;

    @Autowired RateLimiterService rateLimiterService;

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

        String sessionId = request.getHeader(Constants.HTTP_HEADER_TOKEN);

        String ip = RequestUtils.getIp(request);

        if (StringUtils.isAllBlank(ip, sessionId)) {
            sysLogService.saveSysLog(request, "限流模块", "非法请求", "ip和sessionId都为空");
            writeResponse(new BizException("非法请求"), response);
            return;
        }


        if (request.getAttribute(Constants.HTTP_ATTR_RESOURCE) != null) {
            if (BooleanUtils.isFalse(
                    ((ResourceDto) request.getAttribute(Constants.HTTP_ATTR_RESOURCE))
                            .getRateLimit())) {
                doFilter(request, response, filterChain);
                return;


            }
        }


        if (StringUtils.isNotEmpty(sessionId)) {


            RateLimiterService.RateLimitAcquireResult acquireSessionRL = rateLimiterService.acquireSessionRL(sessionId);


            if (acquireSessionRL != RateLimiterService.RateLimitAcquireResult.SUCCESS ) {
                sysLogService.saveSysLog(request, "限流模块", "会话限流", acquireSessionRL.name());

                if (acquireSessionRL == RateLimiterService.RateLimitAcquireResult.ERROR){
                    writeResponse(new BizException("invalidToken","请重新登录"), response);
                    return;
                }

                writeResponse(new BizException("当前会话请求被限流,请稍后再试"), response);
                log.warn(
                        "会话限流: sessionId {} user {} request {} type {}",
                        sessionId,
                        Ctx.currentUser().getUsername(),
                        acquireSessionRL.name(),
                        request.getRequestURI());
                return;
            }

            RateLimiterService.RateLimitAcquireResult acquireUserRL = rateLimiterService.acquireUserRL(Ctx.currentUser().getId());
            if (acquireUserRL != RateLimiterService.RateLimitAcquireResult.SUCCESS ) {
                sysLogService.saveSysLog(request, "限流模块", "用户限流", acquireSessionRL.name());
                if (acquireUserRL == RateLimiterService.RateLimitAcquireResult.ERROR){
                    writeResponse(new BizException("invalidToken","请重新登录"), response);
                    return;
                }
                writeResponse(new BizException("当前会话请求被限流,请稍后再试"), response);
                log.warn(
                        "用户限流: sessionId {} user {} request {} type {}",
                        sessionId,
                        Ctx.currentUser().getUsername(),
                        acquireSessionRL.name(),
                        request.getRequestURI());
                return;
            }

        } else {


            rateLimiterService.initAnonymousIpRL(ip);


            RateLimiterService.RateLimitAcquireResult acquire = rateLimiterService.acquireIpRL(ip);

            if (acquire != RateLimiterService.RateLimitAcquireResult.SUCCESS) {
                sysLogService.saveSysLog(request, "限流模块", "IP限流", acquire.name());
                if (acquire == RateLimiterService.RateLimitAcquireResult.ERROR){
                    writeResponse(new BizException("invalidToken","请重新登录"), response);
                    return;
                }
                writeResponse(new BizException("请求被限流,请稍后再试"), response);
                log.warn("ip {} request {} type {} is rate limited", ip, request.getRequestURI(), acquire.name());
                return;
            }
        }

        filterChain.doFilter(request, response);
    }

    @SneakyThrows
    private void writeResponse(BizException ex, HttpServletResponse response) {

        response.setStatus(200);
        response.setContentType(ContentType.APPLICATION_JSON.getMimeType());
        R<Object> fail = R.fail(ex.getCode(), ex.getMessage());
        response.getOutputStream().write(objectMapper.writeValueAsBytes(fail));
        response.getOutputStream().flush();
    }
}
