package gc.mda.signal_batch.monitoring.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import gc.mda.signal_batch.monitoring.dto.WebSocketUsageLogDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketUsageLoggingInterceptor implements ChannelInterceptor {
    
    // WebSocket 사용 로그 전용 로거
    private static final Logger WS_USAGE_LOGGER = LoggerFactory.getLogger("WEBSOCKET_USAGE");
    
    private final ObjectMapper objectMapper;
    
    // 세션별 연결 정보 추적
    private final ConcurrentHashMap<String, SessionInfo> sessionInfoMap = new ConcurrentHashMap<>();
    
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        
        if (accessor != null) {
            String sessionId = accessor.getSessionId();
            StompCommand command = accessor.getCommand();
            
            try {
                if (command == null) {
                    // command가 null인 경우 간단히 로깅만 하고 처리 종료
                    logWebSocketEvent(WebSocketUsageLogDto.builder()
                            .timestamp(LocalDateTime.now())
                            .sessionId(sessionId)
                            .eventType("UNKNOWN")
                            .stompCommand("NULL_COMMAND")
                            .build());
                    return message;
                }

                switch (command) {
                    case CONNECT -> handleConnect(accessor, sessionId);
                    case SUBSCRIBE -> handleSubscribe(accessor, sessionId);
                    case SEND -> handleSend(accessor, sessionId, message);
                    case UNSUBSCRIBE -> handleUnsubscribe(accessor, sessionId);
                    case DISCONNECT -> handleDisconnect(accessor, sessionId);
                    default -> {
                        // 기타 명령어는 간단히 로깅
                        logWebSocketEvent(WebSocketUsageLogDto.builder()
                                .timestamp(LocalDateTime.now())
                                .sessionId(sessionId)
                                .eventType("OTHER")
                                .stompCommand(command.name())
                                .build());
                    }
                }
            } catch (Exception e) {
                log.error("Failed to log WebSocket usage for session {}", sessionId, e);
            }
        }
        
        return message;
    }
    
    @Override
    public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
        if (ex != null) {
            StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
            if (accessor != null) {
                logWebSocketEvent(WebSocketUsageLogDto.builder()
                        .timestamp(LocalDateTime.now())
                        .sessionId(accessor.getSessionId())
                        .eventType("ERROR")
                        .stompCommand(accessor.getCommand() != null ? accessor.getCommand().name() : "UNKNOWN")
                        .errorMessage(ex.getMessage())
                        .exceptionClass(ex.getClass().getSimpleName())
                        .build());
            }
        }
    }
    
    private void handleConnect(StompHeaderAccessor accessor, String sessionId) {
        LocalDateTime connectTime = LocalDateTime.now();

        // 세션 정보 저장
        SessionInfo sessionInfo = new SessionInfo();
        sessionInfo.connectTime = connectTime;
        sessionInfo.clientIp = getClientIpFromSessionAttributes(accessor);
        sessionInfo.userAgent = getUserAgentFromSessionAttributes(accessor);
        sessionInfoMap.put(sessionId, sessionInfo);

        logWebSocketEvent(WebSocketUsageLogDto.builder()
                .timestamp(connectTime)
                .sessionId(sessionId)
                .clientIp(sessionInfo.clientIp)
                .userAgent(sessionInfo.userAgent)
                .eventType("CONNECT")
                .stompCommand("CONNECT")
                .connectTime(connectTime)
                .build());
    }
    
    private void handleSubscribe(StompHeaderAccessor accessor, String sessionId) {
        SessionInfo sessionInfo = sessionInfoMap.get(sessionId);
        String requestParams = extractRequestParams(accessor);

        logWebSocketEvent(WebSocketUsageLogDto.builder()
                .timestamp(LocalDateTime.now())
                .sessionId(sessionId)
                .clientIp(sessionInfo != null ? sessionInfo.clientIp : null)
                .userAgent(sessionInfo != null ? sessionInfo.userAgent : null)
                .eventType("SUBSCRIBE")
                .stompCommand("SUBSCRIBE")
                .destination(accessor.getDestination())
                .subscriptionId(accessor.getSubscriptionId())
                .requestParams(requestParams)
                .connectTime(sessionInfo != null ? sessionInfo.connectTime : null)
                .build());
    }
    
    private void handleSend(StompHeaderAccessor accessor, String sessionId, Message<?> message) {
        SessionInfo sessionInfo = sessionInfoMap.get(sessionId);
        long payloadSize = getPayloadSize(message);

        // 페이로드에서 파라미터 추출
        String requestParams = extractRequestParamsFromPayload(accessor, message);

        if (sessionInfo != null) {
            sessionInfo.messageCount++;
            sessionInfo.totalDataSent += payloadSize;
        }

        logWebSocketEvent(WebSocketUsageLogDto.builder()
                .timestamp(LocalDateTime.now())
                .sessionId(sessionId)
                .clientIp(sessionInfo != null ? sessionInfo.clientIp : null)
                .userAgent(sessionInfo != null ? sessionInfo.userAgent : null)
                .eventType("MESSAGE")
                .stompCommand("SEND")
                .destination(accessor.getDestination())
                .requestParams(requestParams)
                .payloadSizeBytes(payloadSize)
                .messageCount(sessionInfo != null ? sessionInfo.messageCount : 1)
                .totalDataSent(sessionInfo != null ? sessionInfo.totalDataSent : payloadSize)
                .connectTime(sessionInfo != null ? sessionInfo.connectTime : null)
                .build());
    }
    
    private void handleUnsubscribe(StompHeaderAccessor accessor, String sessionId) {
        SessionInfo sessionInfo = sessionInfoMap.get(sessionId);
        
        logWebSocketEvent(WebSocketUsageLogDto.builder()
                .timestamp(LocalDateTime.now())
                .sessionId(sessionId)
                .clientIp(sessionInfo != null ? sessionInfo.clientIp : null)
                .eventType("UNSUBSCRIBE")
                .stompCommand("UNSUBSCRIBE")
                .subscriptionId(accessor.getSubscriptionId())
                .connectTime(sessionInfo != null ? sessionInfo.connectTime : null)
                .build());
    }
    
    private void handleDisconnect(StompHeaderAccessor accessor, String sessionId) {
        LocalDateTime disconnectTime = LocalDateTime.now();
        SessionInfo sessionInfo = sessionInfoMap.remove(sessionId);
        
        long connectionDuration = 0;
        if (sessionInfo != null && sessionInfo.connectTime != null) {
            connectionDuration = java.time.Duration.between(sessionInfo.connectTime, disconnectTime).toMillis();
        }
        
        logWebSocketEvent(WebSocketUsageLogDto.builder()
                .timestamp(disconnectTime)
                .sessionId(sessionId)
                .clientIp(sessionInfo != null ? sessionInfo.clientIp : null)
                .eventType("DISCONNECT")
                .stompCommand("DISCONNECT")
                .connectTime(sessionInfo != null ? sessionInfo.connectTime : null)
                .disconnectTime(disconnectTime)
                .connectionDurationMs(connectionDuration)
                .messageCount(sessionInfo != null ? sessionInfo.messageCount : 0)
                .totalDataSent(sessionInfo != null ? sessionInfo.totalDataSent : 0)
                .build());
    }
    
    private void logWebSocketEvent(WebSocketUsageLogDto logDto) {
        try {
            String logJson = objectMapper.writeValueAsString(logDto);
            WS_USAGE_LOGGER.info(logJson);
        } catch (Exception e) {
            log.error("Failed to serialize WebSocket usage log", e);
        }
    }
    
    private String getClientIpFromSessionAttributes(StompHeaderAccessor accessor) {
        // HandshakeInterceptor에서 설정된 세션 속성에서 CLIENT_IP 가져오기
        Object clientIp = accessor.getSessionAttributes() != null ?
                          accessor.getSessionAttributes().get("CLIENT_IP") : null;
        return clientIp != null ? clientIp.toString() : "unknown";
    }

    private String getUserAgentFromSessionAttributes(StompHeaderAccessor accessor) {
        // HandshakeInterceptor에서 설정된 세션 속성에서 USER_AGENT 가져오기
        Object userAgent = accessor.getSessionAttributes() != null ?
                           accessor.getSessionAttributes().get("USER_AGENT") : null;
        return userAgent != null ? userAgent.toString() : null;
    }

    private String getHeaderValue(StompHeaderAccessor accessor, String headerName) {
        return accessor.getFirstNativeHeader(headerName);
    }

    /**
     * SUBSCRIBE 요청 파라미터 추출 (헤더 기반)
     */
    private String extractRequestParams(StompHeaderAccessor accessor) {
        StringBuilder params = new StringBuilder();

        // STOMP 헤더에서 파라미터 추출
        String[] paramHeaders = {
            "startTime", "endTime", "timeBucket", "mmsi", "vesselId",
            "areaId", "gridId", "limit", "offset", "minutes"
        };

        for (String headerName : paramHeaders) {
            String value = accessor.getFirstNativeHeader(headerName);
            if (value != null && !value.isEmpty()) {
                if (params.length() > 0) {
                    params.append(", ");
                }
                params.append(headerName).append("=").append(value);
            }
        }

        // 헤더에 없으면 destination만 표시
        if (params.length() == 0) {
            String destination = accessor.getDestination();
            if (destination != null) {
                return "dest=" + destination;
            }
        }

        return params.length() > 0 ? params.toString() : null;
    }

    /**
     * MESSAGE 페이로드에서 파라미터 추출 (JSON 파싱)
     */
    private String extractRequestParamsFromPayload(StompHeaderAccessor accessor, Message<?> message) {
        try {
            // 페이로드를 문자열로 변환
            String payloadStr = null;
            if (message.getPayload() instanceof byte[]) {
                payloadStr = new String((byte[]) message.getPayload(), java.nio.charset.StandardCharsets.UTF_8);
            } else if (message.getPayload() instanceof String) {
                payloadStr = (String) message.getPayload();
            }

            if (payloadStr == null || payloadStr.isEmpty()) {
                return "dest=" + accessor.getDestination();
            }

            // JSON 파싱하여 주요 필드 추출
            com.fasterxml.jackson.databind.JsonNode jsonNode = objectMapper.readTree(payloadStr);
            StringBuilder params = new StringBuilder();

            // 주요 파라미터 필드 추출
            String[] paramFields = {
                "startTime", "endTime", "timeBucket", "mmsi", "vesselId",
                "areaId", "gridId", "limit", "offset", "minutes"
            };

            for (String field : paramFields) {
                if (jsonNode.has(field)) {
                    com.fasterxml.jackson.databind.JsonNode fieldNode = jsonNode.get(field);
                    if (!fieldNode.isNull()) {
                        if (params.length() > 0) {
                            params.append(", ");
                        }
                        params.append(field).append("=").append(fieldNode.asText());
                    }
                }
            }

            if (params.length() == 0) {
                return "dest=" + accessor.getDestination();
            }

            return params.toString();

        } catch (Exception e) {
            // 파싱 실패시 destination만 반환
            log.debug("Failed to parse payload for params extraction: {}", e.getMessage());
            return "dest=" + accessor.getDestination();
        }
    }

    private long getPayloadSize(Message<?> message) {
        if (message.getPayload() instanceof byte[]) {
            return ((byte[]) message.getPayload()).length;
        } else if (message.getPayload() instanceof String) {
            return ((String) message.getPayload()).length();
        }
        return 0;
    }
    
    private static class SessionInfo {
        LocalDateTime connectTime;
        String clientIp;
        String userAgent;
        int messageCount = 0;
        long totalDataSent = 0;
    }
}