package gc.mda.kcg.audit;

import gc.mda.kcg.auth.AuthPrincipal;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;

/**
 * 모든 HTTP 요청을 auth_access_log에 기록.
 * 비동기 큐 기반 — 요청 처리 지연 최소화.
 */
@Slf4j
@Component
@Order(100)  // JwtAuthFilter(Spring 기본 -100) 이후 실행
@RequiredArgsConstructor
public class AccessLogFilter extends OncePerRequestFilter {

    private final AccessLogRepository accessLogRepository;
    private static final BlockingQueue<AccessLog> QUEUE = new ArrayBlockingQueue<>(10000);
    private static volatile boolean workerStarted = false;

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws ServletException, IOException {

        long start = System.currentTimeMillis();
        try {
            chain.doFilter(req, res);
        } finally {
            ensureWorkerStarted();
            try {
                AuthPrincipal principal = currentPrincipal();
                AccessLog log = AccessLog.builder()
                        .userId(principal != null ? principal.getUserId() : null)
                        .userAcnt(principal != null ? principal.getUserAcnt() : null)
                        .httpMethod(req.getMethod())
                        .requestPath(req.getRequestURI())
                        .queryString(req.getQueryString())
                        .statusCode(res.getStatus())
                        .durationMs((int) (System.currentTimeMillis() - start))
                        .ipAddress(extractIp(req))
                        .userAgent(req.getHeader("User-Agent"))
                        .build();
                QUEUE.offer(log);
            } catch (Exception ignored) {
                // 접근 로그 실패가 응답을 막지 않도록
            }
        }
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest req) {
        String path = req.getRequestURI();
        return path.startsWith("/actuator/health") || path.startsWith("/error") || path.equals("/favicon.ico");
    }

    private void ensureWorkerStarted() {
        if (workerStarted) return;
        synchronized (AccessLogFilter.class) {
            if (workerStarted) return;
            workerStarted = true;
            Executors.newSingleThreadExecutor(r -> {
                Thread t = new Thread(r, "access-log-writer");
                t.setDaemon(true);
                return t;
            }).submit(() -> {
                while (true) {
                    try {
                        AccessLog log = QUEUE.take();
                        accessLogRepository.save(log);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    } catch (Exception e) {
                        AccessLogFilter.log.error("AccessLog 저장 실패", e);
                    }
                }
            });
        }
    }

    private AuthPrincipal currentPrincipal() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.getPrincipal() instanceof AuthPrincipal p) return p;
        return null;
    }

    private String extractIp(HttpServletRequest req) {
        String fwd = req.getHeader("X-Forwarded-For");
        if (fwd != null && !fwd.isBlank()) return fwd.split(",")[0].trim();
        return req.getRemoteAddr();
    }
}
