package gc.mda.signal_batch.monitoring.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import gc.mda.signal_batch.monitoring.dto.ApiUsageLogDto;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.UUID;

@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@RequiredArgsConstructor
public class ApiUsageLoggingFilter implements Filter {
    
    // API 사용 로그 전용 로거
    private static final Logger API_USAGE_LOGGER = LoggerFactory.getLogger("API_USAGE");
    
    private final ObjectMapper objectMapper;
    
    // 로깅 제외할 경로들
    private static final String[] EXCLUDE_PATHS = {
        "/actuator/", "/swagger", "/v3/api-docs", "/favicon.ico", 
        "/webjars/", "/error", "/css/", "/js/", "/images/"
    };

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
            chain.doFilter(request, response);
            return;
        }

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 제외 경로 확인
        if (shouldExclude(httpRequest.getRequestURI())) {
            chain.doFilter(request, response);
            return;
        }

        // 요청/응답을 캐싱 가능한 래퍼로 감싸기
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(httpRequest);
        ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(httpResponse);

        long startTime = System.currentTimeMillis();
        String requestId = UUID.randomUUID().toString().substring(0, 8);

        try {
            // 실제 요청 처리
            chain.doFilter(wrappedRequest, wrappedResponse);
        } catch (Exception e) {
            // 에러가 발생해도 로그는 남김
            logApiUsage(wrappedRequest, wrappedResponse, startTime, requestId, e);
            throw e;
        }

        // 정상 처리된 경우 로그 기록
        logApiUsage(wrappedRequest, wrappedResponse, startTime, requestId, null);
        
        // 응답을 실제 클라이언트에게 복사
        wrappedResponse.copyBodyToResponse();
    }

    private void logApiUsage(ContentCachingRequestWrapper request, 
                           ContentCachingResponseWrapper response,
                           long startTime, String requestId, Exception exception) {
        try {
            long processingTime = System.currentTimeMillis() - startTime;
            
            ApiUsageLogDto logDto = ApiUsageLogDto.builder()
                    .timestamp(LocalDateTime.now())
                    .requestId(requestId)
                    .sessionId(request.getSession(false) != null ? request.getSession().getId() : null)
                    .clientIp(getClientIP(request))
                    .method(request.getMethod())
                    .apiPath(request.getRequestURI())
                    .queryString(request.getQueryString())
                    .userAgent(request.getHeader("User-Agent"))
                    .referer(request.getHeader("Referer"))
                    .statusCode(response.getStatus())
                    .responseSizeBytes(response.getContentSize())
                    .processingTimeMs(processingTime)
                    .errorMessage(exception != null ? exception.getMessage() : null)
                    .exceptionClass(exception != null ? exception.getClass().getSimpleName() : null)
                    .build();

            // JSON 형태로 전용 로거에 기록
            String logJson = objectMapper.writeValueAsString(logDto);
            API_USAGE_LOGGER.info(logJson);

        } catch (Exception e) {
            log.error("Failed to log API usage", e);
        }
    }

    private String getClientIP(HttpServletRequest request) {
        // X-Forwarded-For 헤더 확인 (프록시/로드밸런서 환경)
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            return xForwardedFor.split(",")[0].trim();
        }
        
        // X-Real-IP 헤더 확인
        String xRealIp = request.getHeader("X-Real-IP");
        if (xRealIp != null && !xRealIp.isEmpty()) {
            return xRealIp;
        }
        
        // X-Forwarded-Proto 와 함께 오는 경우
        String xOriginalFor = request.getHeader("X-Original-Forwarded-For");
        if (xOriginalFor != null && !xOriginalFor.isEmpty()) {
            return xOriginalFor.split(",")[0].trim();
        }
        
        // 기본 Remote Address
        return request.getRemoteAddr();
    }

    private boolean shouldExclude(String uri) {
        if (uri == null) return true;
        
        for (String excludePath : EXCLUDE_PATHS) {
            if (uri.contains(excludePath)) {
                return true;
            }
        }
        
        // /api/ 로 시작하는 것만 로깅 (필요에 따라 수정)
        return !uri.startsWith("/api/");
    }
}