package gc.mda.signal_batch.global.config;

import gc.mda.signal_batch.global.websocket.interceptor.TrackQueryInterceptor;
import gc.mda.signal_batch.monitoring.interceptor.WebSocketUsageLoggingInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.*;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;

import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.List;
import java.util.Map;
import java.util.UUID;


@Configuration
@EnableWebSocketMessageBroker
@RequiredArgsConstructor
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {

    private final TrackQueryInterceptor trackQueryInterceptor;
    private final WebSocketUsageLoggingInterceptor webSocketUsageLoggingInterceptor;
    private final ObjectMapper objectMapper; // JacksonConfig에서 생성된 ObjectMapper 주입
    private final WebSocketProperties webSocketProperties;
    
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        // 청크 최대 1024KB → 버퍼 2MB면 충분. 256MB는 세션당 Humongous 객체 유발 → OOM 근본 원인
        container.setMaxTextMessageBufferSize(2 * 1024 * 1024);    // 256MB → 2MB
        container.setMaxBinaryMessageBufferSize(2 * 1024 * 1024);  // 256MB → 2MB
        container.setMaxSessionIdleTimeout(webSocketProperties.getSession().getIdleTimeoutMs());
        return container;
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 1. 네이티브 WebSocket 엔드포인트 (프론트엔드 @stomp/stompjs 지원)
        registry.addEndpoint("/ws-tracks")
                .setHandshakeHandler(new CustomHandshakeHandler())
                .addInterceptors(new ClientIpHandshakeInterceptor())
                .setAllowedOriginPatterns("*");

        // 2. SockJS 폴백 엔드포인트 (레거시 브라우저 및 기존 클라이언트 지원)
        registry.addEndpoint("/ws-tracks")
                .setHandshakeHandler(new CustomHandshakeHandler())
                .addInterceptors(new ClientIpHandshakeInterceptor())
                .setAllowedOriginPatterns("*")
                .withSockJS()
                .setClientLibraryUrl("/static/libs/js/sockjs.min.js")
                .setStreamBytesLimit(5 * 1024 * 1024)  // 100MB → 5MB (SockJS 폴링 버퍼)
                .setHttpMessageCacheSize(1000)
                .setDisconnectDelay(webSocketProperties.getSession().getSockjsDisconnectDelayMs())
                .setWebSocketEnabled(true)
                .setSuppressCors(false);
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 인메모리 메시지 브로커 설정
        registry.enableSimpleBroker("/topic", "/queue")
                .setTaskScheduler(messageBrokerTaskScheduler())
                .setHeartbeatValue(new long[]{
                        webSocketProperties.getSession().getServerHeartbeatMs(),
                        webSocketProperties.getSession().getClientHeartbeatMs()
                });

        // 애플리케이션 destination prefix
        registry.setApplicationDestinationPrefixes("/app");
        
        // 사용자별 destination prefix
        registry.setUserDestinationPrefix("/user");
        
        // 캐시 크기 제한
        registry.setCacheLimit(2048);  // 1024 -> 2048로 증가
    }

    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
        registration
            .setMessageSizeLimit(webSocketProperties.getTransport().getMessageSizeLimitMb() * 1024 * 1024)
            .setSendBufferSizeLimit(webSocketProperties.getTransport().getSendBufferSizeLimitMb() * 1024 * 1024)
            .setSendTimeLimit(webSocketProperties.getSession().getSendTimeLimitSeconds() * 1000)
            .setTimeToFirstMessage(30 * 1000);
        
        // 에러 핸들러 등록은 별도로 처리
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        WebSocketProperties.TransportProperties tp = webSocketProperties.getTransport();
        registration
            .interceptors(webSocketUsageLoggingInterceptor, trackQueryInterceptor)
            .taskExecutor()
            .corePoolSize(tp.getInboundCorePoolSize())
            .maxPoolSize(tp.getInboundMaxPoolSize())
            .queueCapacity(tp.getInboundQueueCapacity());
    }

    @Override
    public void configureClientOutboundChannel(ChannelRegistration registration) {
        WebSocketProperties.TransportProperties tp = webSocketProperties.getTransport();
        registration
            .taskExecutor()
            .corePoolSize(tp.getOutboundCorePoolSize())
            .maxPoolSize(tp.getOutboundMaxPoolSize())
            .queueCapacity(tp.getOutboundQueueCapacity());
    }

    @Override
    public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
        // Use the ObjectMapper from JacksonConfig which already has JavaTimeModule configured
        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
        converter.setObjectMapper(objectMapper);
        converter.setPrettyPrint(false);
        
        // Clear default converters and add our custom one
        messageConverters.clear();
        messageConverters.add(converter);
        
        return true; // Don't add default converters
    }

    @Bean
    public TaskScheduler messageBrokerTaskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(4);
        scheduler.setThreadNamePrefix("wsMsgBroker-");
        scheduler.setAwaitTerminationSeconds(60);
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.initialize();
        return scheduler;
    }

    // 세션별 고유 ID 생성을 위한 HandshakeHandler
    private static class CustomHandshakeHandler extends DefaultHandshakeHandler {
        @Override
        protected Principal determineUser(ServerHttpRequest request,
                                        WebSocketHandler wsHandler,
                                        Map<String, Object> attributes) {
            return new StompPrincipal(UUID.randomUUID().toString());
        }
    }
    
    private static class StompPrincipal implements Principal {
        private final String name;

        public StompPrincipal(String name) {
            this.name = name;
        }

        @Override
        public String getName() {
            return name;
        }
    }

    // 클라이언트 IP 추출을 위한 HandshakeInterceptor
    private static class ClientIpHandshakeInterceptor implements HandshakeInterceptor {

        @Override
        public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                      WebSocketHandler wsHandler, Map<String, Object> attributes) {
            // HTTP 요청에서 클라이언트 IP 추출
            String clientIp = extractClientIp(request);
            attributes.put("CLIENT_IP", clientIp);

            // User-Agent 추출
            if (request instanceof ServletServerHttpRequest) {
                HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
                String userAgent = servletRequest.getHeader("User-Agent");
                attributes.put("USER_AGENT", userAgent);
            }

            return true;
        }

        @Override
        public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                  WebSocketHandler wsHandler, Exception exception) {
            // 핸드셰이크 완료 후 처리 (필요시 사용)
        }

        private String extractClientIp(ServerHttpRequest request) {
            if (request instanceof ServletServerHttpRequest) {
                HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();

                // X-Forwarded-For 헤더 확인 (프록시/로드밸런서 환경)
                String xForwardedFor = servletRequest.getHeader("X-Forwarded-For");
                if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
                    return xForwardedFor.split(",")[0].trim();
                }

                // X-Real-IP 헤더 확인
                String xRealIp = servletRequest.getHeader("X-Real-IP");
                if (xRealIp != null && !xRealIp.isEmpty()) {
                    return xRealIp;
                }

                // X-Original-Forwarded-For 헤더 확인
                String xOriginalFor = servletRequest.getHeader("X-Original-Forwarded-For");
                if (xOriginalFor != null && !xOriginalFor.isEmpty()) {
                    return xOriginalFor.split(",")[0].trim();
                }

                // 기본 Remote Address
                return servletRequest.getRemoteAddr();
            }

            // ServletServerHttpRequest가 아닌 경우 기본값
            return "unknown";
        }
    }
}