package com.unicorn.hgzero.meeting.infra.websocket;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;

import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * WebSocket 메시지 핸들러
 * 회의록 실시간 협업을 위한 WebSocket 연결 및 메시지 처리
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class WebSocketHandler implements org.springframework.web.socket.WebSocketHandler {

    private final CollaborationMessageHandler collaborationMessageHandler;
    private final ObjectMapper objectMapper;

    // 회의록별 연결된 세션 목록
    private final Map<String, List<WebSocketSession>> minutesSessions = new ConcurrentHashMap<>();
    // 세션별 사용자 정보
    private final Map<String, UserInfo> sessionUsers = new ConcurrentHashMap<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String minutesId = extractMinutesId(session);
        String userId = extractUserId(session);
        String userName = extractUserName(session);

        if (minutesId == null || userId == null || userName == null) {
            log.warn("WebSocket 연결 실패 - 필수 매개변수 누락: minutesId={}, userId={}, userName={}", 
                    minutesId, userId, userName);
            session.close(CloseStatus.BAD_DATA);
            return;
        }

        // 세션 등록
        minutesSessions.computeIfAbsent(minutesId, k -> new CopyOnWriteArrayList<>()).add(session);
        sessionUsers.put(session.getId(), new UserInfo(userId, userName, minutesId));

        log.info("WebSocket 연결 성공 - sessionId: {}, minutesId: {}, userId: {}, userName: {}", 
                session.getId(), minutesId, userId, userName);

        // 사용자 입장 알림 전송
        broadcastToMinutes(minutesId, CollaborationMessage.builder()
                .type("USER_JOINED")
                .minutesId(minutesId)
                .userId(userId)
                .userName(userName)
                .timestamp(System.currentTimeMillis())
                .build(), session.getId());
    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        if (!(message instanceof TextMessage)) {
            log.warn("WebSocket 메시지 타입 오류 - sessionId: {}", session.getId());
            return;
        }

        String payload = ((TextMessage) message).getPayload();
        UserInfo userInfo = sessionUsers.get(session.getId());
        
        if (userInfo == null) {
            log.warn("WebSocket 사용자 정보 없음 - sessionId: {}", session.getId());
            return;
        }

        try {
            // 메시지 파싱 및 처리
            CollaborationMessage collaborationMessage = objectMapper.readValue(payload, CollaborationMessage.class);
            collaborationMessage.setUserId(userInfo.getUserId());
            collaborationMessage.setUserName(userInfo.getUserName());
            collaborationMessage.setMinutesId(userInfo.getMinutesId());
            collaborationMessage.setTimestamp(System.currentTimeMillis());

            log.debug("WebSocket 메시지 수신 - sessionId: {}, type: {}, minutesId: {}", 
                    session.getId(), collaborationMessage.getType(), userInfo.getMinutesId());

            // 협업 메시지 처리
            CollaborationMessage response = collaborationMessageHandler.handleMessage(collaborationMessage);
            
            if (response != null) {
                // 다른 사용자들에게 브로드캐스트
                broadcastToMinutes(userInfo.getMinutesId(), response, session.getId());
            }

        } catch (Exception e) {
            log.error("WebSocket 메시지 처리 실패 - sessionId: {}, payload: {}", 
                    session.getId(), payload, e);
            
            // 오류 메시지 전송
            sendToSession(session, CollaborationMessage.builder()
                    .type("ERROR")
                    .content("메시지 처리 중 오류가 발생했습니다")
                    .timestamp(System.currentTimeMillis())
                    .build());
        }
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        log.error("WebSocket 전송 오류 - sessionId: {}", session.getId(), exception);
        removeSession(session);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        UserInfo userInfo = sessionUsers.get(session.getId());
        
        if (userInfo != null) {
            log.info("WebSocket 연결 종료 - sessionId: {}, minutesId: {}, userId: {}, closeStatus: {}", 
                    session.getId(), userInfo.getMinutesId(), userInfo.getUserId(), closeStatus);

            // 사용자 퇴장 알림 전송
            broadcastToMinutes(userInfo.getMinutesId(), CollaborationMessage.builder()
                    .type("USER_LEFT")
                    .minutesId(userInfo.getMinutesId())
                    .userId(userInfo.getUserId())
                    .userName(userInfo.getUserName())
                    .timestamp(System.currentTimeMillis())
                    .build(), session.getId());
        }

        removeSession(session);
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * 특정 회의록에 연결된 모든 사용자에게 메시지 브로드캐스트
     */
    public void broadcastToMinutes(String minutesId, CollaborationMessage message, String excludeSessionId) {
        List<WebSocketSession> sessions = minutesSessions.get(minutesId);
        if (sessions == null) return;

        String messageJson;
        try {
            messageJson = objectMapper.writeValueAsString(message);
        } catch (Exception e) {
            log.error("메시지 JSON 직렬화 실패", e);
            return;
        }

        sessions.removeIf(session -> {
            try {
                if (!session.getId().equals(excludeSessionId) && session.isOpen()) {
                    session.sendMessage(new TextMessage(messageJson));
                    return false;
                } else if (!session.isOpen()) {
                    sessionUsers.remove(session.getId());
                    return true; // 닫힌 세션 제거
                }
                return false;
            } catch (IOException e) {
                log.warn("WebSocket 메시지 전송 실패 - sessionId: {}", session.getId(), e);
                sessionUsers.remove(session.getId());
                return true; // 전송 실패 세션 제거
            }
        });
    }

    /**
     * 특정 세션에 메시지 전송
     */
    private void sendToSession(WebSocketSession session, CollaborationMessage message) {
        try {
            if (session.isOpen()) {
                String messageJson = objectMapper.writeValueAsString(message);
                session.sendMessage(new TextMessage(messageJson));
            }
        } catch (IOException e) {
            log.warn("WebSocket 메시지 전송 실패 - sessionId: {}", session.getId(), e);
        }
    }

    /**
     * 세션 제거
     */
    private void removeSession(WebSocketSession session) {
        UserInfo userInfo = sessionUsers.remove(session.getId());
        if (userInfo != null) {
            List<WebSocketSession> sessions = minutesSessions.get(userInfo.getMinutesId());
            if (sessions != null) {
                sessions.remove(session);
                if (sessions.isEmpty()) {
                    minutesSessions.remove(userInfo.getMinutesId());
                }
            }
        }
    }

    /**
     * URI에서 회의록 ID 추출
     */
    private String extractMinutesId(WebSocketSession session) {
        URI uri = session.getUri();
        if (uri != null) {
            String path = uri.getPath();
            // /ws/minutes/{minutesId} 패턴에서 minutesId 추출
            String[] pathSegments = path.split("/");
            if (pathSegments.length >= 4 && "ws".equals(pathSegments[1]) && "minutes".equals(pathSegments[2])) {
                return pathSegments[3];
            }
        }
        return null;
    }

    /**
     * 헤더에서 사용자 ID 추출
     */
    private String extractUserId(WebSocketSession session) {
        return session.getHandshakeHeaders().getFirst("X-User-Id");
    }

    /**
     * 헤더에서 사용자 이름 추출
     */
    private String extractUserName(WebSocketSession session) {
        return session.getHandshakeHeaders().getFirst("X-User-Name");
    }

    /**
     * 사용자 정보 저장 클래스
     */
    private static class UserInfo {
        private final String userId;
        private final String userName;
        private final String minutesId;

        public UserInfo(String userId, String userName, String minutesId) {
            this.userId = userId;
            this.userName = userName;
            this.minutesId = minutesId;
        }

        public String getUserId() { return userId; }
        public String getUserName() { return userName; }
        public String getMinutesId() { return minutesId; }
    }
}