/**
 * 리플레이 항적 조회 WebSocket 서비스
 * 참조: mda-react-front/src/tracking/services/TrackingWebSocketService.ts
 *
 * STOMP 프로토콜 기반 WebSocket으로 선박 항적 데이터를 청크 단위로 수신
 * - brokerURL 직접 연결 (SockJS 미사용)
 * - 청크 기반 대용량 데이터 수신
 * - timestamp 기반 진행률 추적
 * - 쿼리 취소 및 타임아웃 관리
 *
 * @singleton 애플리케이션 전체에서 하나의 인스턴스만 사용
 */
import { Client } from '@stomp/stompjs';
import { transformExtent } from 'ol/proj';
import {
  ConnectionState,
  isTrackChunkResponse,
  isQueryStatusUpdate,
  normalizeChunkResponse,
  extractTracks,
} from '../types/replay.types';
import useReplayStore from '../stores/replayStore';
import useMergedTrackStore from '../stores/mergedTrackStore';

// WebSocket 엔드포인트 (환경 변수)
const WS_ENDPOINT = import.meta.env.VITE_TRACKING_WS;

// 타임아웃 설정
const CONNECTION_TIMEOUT = 10000; // 10초
const QUERY_TIMEOUT = 300000; // 5분

/**
 * ReplayWebSocketService
 */
class ReplayWebSocketService {
  constructor() {
    this.client = null;
    this.subscriptions = [];
    this.isConnecting = false;
    this.currentQueryId = null;
    this.connectionPromise = null;
    this.queryTimeoutId = null;

    // timestamp 기반 진행률 추적
    this.queryStartTimestamp = 0;
    this.queryEndTimestamp = 0;
    this.maxReceivedTimestamp = 0;
    this.estimatedProgress = 0;
  }

  // ===== 공개 API =====

  /**
   * 쿼리 실행 (메인 메서드)
   *
   * 1. WebSocket 연결 확인/생성
   * 2. 채널 구독
   * 3. 쿼리 전송
   * 4. 완료 시 자동 정리
   *
   * @param {Object} request - 항적 조회 요청
   * @param {string} request.startTime - 시작 시간 (ISO 형식, KST)
   * @param {string} request.endTime - 종료 시간 (ISO 형식, KST)
   * @param {string[]} [request.vesselIds] - 조회 대상 선박 ID (빈 배열이면 전체 조회)
   */
  async executeQuery(request) {
    try {
      // 이전 쿼리가 진행 중이면 취소
      if (this.currentQueryId) {
        await this.cancelQuery();
      }

      // 스토어 초기화
      const replayStore = useReplayStore.getState();
      replayStore.reset();
      useMergedTrackStore.getState().clear();

      replayStore.setCurrentQuery(request);
      replayStore.setConnectionState(ConnectionState.CONNECTING);

      // 연결 확보
      await this._ensureConnected();

      // 쿼리 ID 생성
      this.currentQueryId = `query_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
      replayStore.setQueryId(this.currentQueryId);

      // 쿼리 타임아웃 설정
      this._setQueryTimeout();

      // 쿼리 전송
      this._sendQuery(request);
    } catch (error) {
      console.error('[ReplayWS] 쿼리 실행 실패:', error);
      this._handleError(error);
      throw error;
    }
  }

  /**
   * 쿼리 취소
   */
  async cancelQuery() {
    if (!this.currentQueryId) return;

    try {
      if (this.client?.connected) {
        this.client.publish({
          destination: `/app/tracks/cancel/${this.currentQueryId}`,
          body: '',
        });
      }
    } catch (error) {
      console.error('[ReplayWS] 취소 요청 실패:', error);
    } finally {
      this._cleanup();
    }
  }

  /**
   * 연결 해제
   */
  disconnect() {
    this._cleanup();
  }

  /**
   * 연결 상태 확인
   */
  get connected() {
    return this.client?.connected || false;
  }

  // ===== 연결 관리 (private) =====

  /**
   * 연결 확보 (기존 연결 재사용 또는 새 연결 생성)
   */
  async _ensureConnected() {
    if (this.client?.connected) return;

    if (this.isConnecting && this.connectionPromise) {
      await this.connectionPromise;
      return;
    }

    this.connectionPromise = this._createConnection();
    await this.connectionPromise;
  }

  /**
   * WebSocket 연결 생성
   * 참조: mda-react-front TrackingWebSocketService.createConnection()
   */
  async _createConnection() {
    this.isConnecting = true;

    try {
      this.client = new Client({
        brokerURL: WS_ENDPOINT,

        // 재연결: 수동 관리 (자동 재연결 비활성화)
        reconnectDelay: 0,

        // 연결 타임아웃
        connectionTimeout: CONNECTION_TIMEOUT,

        // 하트비트 (10초)
        heartbeatIncoming: 10000,
        heartbeatOutgoing: 10000,

        onConnect: () => {
          this.isConnecting = false;
          this._updateConnectionState(ConnectionState.CONNECTED);
          this._setupSubscriptions();
        },

        onStompError: (frame) => {
          console.error('[ReplayWS] STOMP 에러:', frame);
          this._handleError(new Error(`STOMP 에러: ${frame.headers?.message || '알 수 없는 오류'}`));
        },

        onDisconnect: (frame) => {
          this._updateConnectionState(ConnectionState.DISCONNECTED);

          // 비정상 종료 감지
          if (this.currentQueryId && !frame?.headers?.['normal-close']) {
            this._handleAbnormalClose();
          }
        },

        onWebSocketError: (event) => {
          console.error('[ReplayWS] WebSocket 에러:', event);
          if (event.type === 'close' && event.code === 1006) {
            this._handleAbnormalClose();
          }
        },
      });

      // 연결 시작
      this._updateConnectionState(ConnectionState.CONNECTING);
      this.client.activate();

      // 연결 완료 대기 (폴링)
      await new Promise((resolve, reject) => {
        const timeout = setTimeout(() => {
          reject(new Error('연결 타임아웃'));
        }, CONNECTION_TIMEOUT);

        const checkInterval = setInterval(() => {
          if (this.client?.connected) {
            clearTimeout(timeout);
            clearInterval(checkInterval);
            resolve();
          }
        }, 100);
      });
    } catch (error) {
      console.error('[ReplayWS] 연결 생성 실패:', error);
      this.isConnecting = false;
      this._updateConnectionState(ConnectionState.ERROR);
      throw error;
    }
  }

  // ===== 구독 관리 (private) =====

  /**
   * 구독 설정
   * 참조: mda-react-front TrackingWebSocketService.setupSubscriptions()
   */
  _setupSubscriptions() {
    if (!this.client?.connected) {
      console.error('[ReplayWS] 구독 설정 실패: 연결되지 않음');
      return;
    }

    // 이전 구독 정리
    this._clearSubscriptions();

    // 1. 청크 데이터 구독
    const chunkSub = this.client.subscribe('/user/queue/tracks/chunk', (message) => {
      this._handleChunkMessage(message);
    });
    this.subscriptions.push(chunkSub);

    // 2. 상태 업데이트 구독
    const statusSub = this.client.subscribe('/user/queue/tracks/status', (message) => {
      this._handleStatusMessage(message);
    });
    this.subscriptions.push(statusSub);

    // 3. 쿼리 응답 구독
    const responseSub = this.client.subscribe('/user/queue/tracks/response', (message) => {
      this._handleResponseMessage(message);
    });
    this.subscriptions.push(responseSub);

    // 4. 에러 구독
    const errorSub = this.client.subscribe('/user/queue/errors', (message) => {
      this._handleErrorMessage(message);
    });
    this.subscriptions.push(errorSub);
  }

  _clearSubscriptions() {
    this.subscriptions.forEach((sub) => {
      try { sub.unsubscribe(); } catch (e) { /* ignore */ }
    });
    this.subscriptions = [];
  }

  // ===== 쿼리 전송 (private) =====

  /**
   * 쿼리 전송
   * 참조: mda-react-front TrackingWebSocketService.sendQuery()
   *
   * - startTime을 HH:00:00 형식으로 정규화 (hourly 테이블 최적화)
   * - chunkedMode: true, chunkSize: 20000 강제 설정
   */
  _sendQuery(request) {
    if (!this.client?.connected) {
      throw new Error('WebSocket이 연결되지 않았습니다');
    }

    // 시작 시간 정규화: HH:00:00 (hourly 테이블 최적화)
    const startTimeStr = typeof request.startTime === 'string'
      ? request.startTime
      : request.startTime.toString();
    const [datePart, timePart] = startTimeStr.split('T');
    const [hour] = timePart ? timePart.split(':') : ['00'];
    const normalizedStartTime = `${datePart}T${hour}:00:00`;

    // vesselIds 정리
    const vesselIds = request.vesselIds && Array.isArray(request.vesselIds) && request.vesselIds.length > 0
      ? request.vesselIds
      : undefined;

    if (!vesselIds) {
      console.warn('[ReplayWS] vesselIds 비어있음 → 전체 선박 조회');
    }

    // 지도 뷰포트 추출 (EPSG:3857 → EPSG:4326)
    const viewport = this._getMapViewport();
    const zoomLevel = this._getMapZoomLevel();

    // 요청 객체 구성 (메인 프로젝트와 동일)
    const enrichedRequest = {
      ...request,
      startTime: normalizedStartTime,
      vesselIds,
      chunkedMode: true,
      chunkSize: 20000,
      viewport,
      simplificationMode: 'AUTO',
      zoomLevel,
      minDistance: 50,
      isIntegration: '0',
    };

    // timestamp 기반 진행률 추적을 위한 시간 저장
    try {
      this.queryStartTimestamp = Math.floor(new Date(request.startTime).getTime() / 1000);
      this.queryEndTimestamp = Math.floor(new Date(request.endTime).getTime() / 1000);
      this.maxReceivedTimestamp = this.queryStartTimestamp;
      this.estimatedProgress = 0;
    } catch (error) {
      console.error('[ReplayWS] timestamp 파싱 실패:', error);
    }

    this.client.publish({
      destination: '/app/tracks/query',
      body: JSON.stringify(enrichedRequest),
    });
  }

  // ===== 메시지 핸들러 (private) =====

  /**
   * 청크 메시지 처리
   * 참조: mda-react-front TrackingWebSocketService.handleChunkMessage()
   */
  _handleChunkMessage(message) {
    try {
      const chunk = JSON.parse(message.body);
      const normalized = this._normalizeChunkResponse(chunk);

      if (!isTrackChunkResponse(normalized)) {
        console.error('[ReplayWS] 잘못된 청크 형식:', chunk);
        return;
      }

      const tracks = extractTracks(normalized);
      if (!tracks || tracks.length === 0) return;

      // tracks를 정규화된 필드로 설정
      normalized.tracks = tracks;

      // 청크 저장
      useMergedTrackStore.getState().addChunkOptimized(normalized);
      useMergedTrackStore.getState().addRawChunk(normalized);

      // timestamp 기반 진행률 계산
      this._updateProgressByTimestamp(tracks, normalized.isLastChunk || false);

      // 스토어 진행률 업데이트
      const replayStore = useReplayStore.getState();
      replayStore.updateProgress(
        replayStore.receivedChunks + 1,
        normalized.totalChunks || replayStore.totalChunks,
        Date.now()
      );
    } catch (error) {
      console.error('[ReplayWS] 청크 처리 오류:', error);
    }
  }

  /**
   * 상태 메시지 처리
   * 참조: mda-react-front TrackingWebSocketService.handleStatusMessage()
   */
  _handleStatusMessage(message) {
    try {
      const status = JSON.parse(message.body);

      if (!isQueryStatusUpdate(status)) {
        console.error('[ReplayWS] 잘못된 상태 형식:', status);
        return;
      }

      if (status.status === 'COMPLETED') {
        useReplayStore.getState().setQueryCompleted(true);
        this._handleQueryComplete();
      }

      if (status.status === 'ERROR') {
        this._handleError(new Error(status.error || '쿼리 처리 중 오류 발생'));
      }

      // 서버에서 queryId를 반환하면 저장
      if (status.queryId) {
        useReplayStore.getState().setQueryId(status.queryId);
      }
    } catch (error) {
      console.error('[ReplayWS] 상태 처리 오류:', error);
    }
  }

  /**
   * 응답 메시지 처리
   */
  _handleResponseMessage(message) {
    try {
      const response = JSON.parse(message.body);

      if (response.queryId) {
        useReplayStore.getState().setQueryId(response.queryId);
      }
    } catch (error) {
      console.error('[ReplayWS] 응답 처리 오류:', error);
    }
  }

  /**
   * 에러 메시지 처리
   */
  _handleErrorMessage(message) {
    console.error('[ReplayWS] 서버 에러:', message.body);
    this._handleError(new Error(message.body));
  }

  // ===== 진행률 (private) =====

  /**
   * timestamp 기반 진행률 업데이트
   * 참조: mda-react-front TrackingWebSocketService.updateProgressByTimestamp()
   */
  _updateProgressByTimestamp(tracks, isLastChunk) {
    try {
      tracks.forEach((track) => {
        if (track.timestamps && Array.isArray(track.timestamps)) {
          track.timestamps.forEach((ts) => {
            const timestamp = typeof ts === 'number' ? ts : parseInt(ts, 10);
            if (timestamp > this.maxReceivedTimestamp) {
              this.maxReceivedTimestamp = timestamp;
            }
          });
        }
      });

      const totalDuration = this.queryEndTimestamp - this.queryStartTimestamp;
      if (totalDuration > 0) {
        const progress = ((this.maxReceivedTimestamp - this.queryStartTimestamp) / totalDuration) * 100;
        this.estimatedProgress = Math.min(progress, 99);
      }

      if (isLastChunk) {
        this.estimatedProgress = 100;
        useReplayStore.getState().setQueryCompleted(true);
      }

      // 스토어에 진행률 반영
      useReplayStore.getState().setProgress(this.estimatedProgress);
    } catch (error) {
      console.error('[ReplayWS] 진행률 계산 오류:', error);
    }
  }

  // ===== 완료/에러/정리 (private) =====

  /**
   * 쿼리 완료 처리
   */
  _handleQueryComplete() {
    this._clearQueryTimeout();

    useReplayStore.setState({ progress: 100 });

    // 자동 연결 해제 및 정리
    this._cleanup();
  }

  /**
   * 에러 처리
   */
  _handleError(error) {
    console.error('[ReplayWS] 에러:', error);

    const userMessage = this._getUserFriendlyError(error);
    useReplayStore.setState({
      connectionState: ConnectionState.ERROR,
    });

    this._cleanup();
  }

  /**
   * 비정상 종료 처리 (버퍼 오버플로우 등)
   */
  _handleAbnormalClose() {
    console.error('[ReplayWS] 비정상 종료 (버퍼 오버플로우 가능)');
    this._cleanup();
  }

  /**
   * 사용자 친화적 에러 메시지
   */
  _getUserFriendlyError(error) {
    const message = error.message.toLowerCase();
    if (message.includes('timeout')) return '서버 응답 시간이 초과되었습니다.';
    if (message.includes('network') || message.includes('connect')) return '네트워크 연결을 확인해주세요.';
    return '데이터 조회 중 오류가 발생했습니다.';
  }

  // ===== 타임아웃 (private) =====

  _setQueryTimeout() {
    this._clearQueryTimeout();
    this.queryTimeoutId = setTimeout(() => {
      console.error('[ReplayWS] 쿼리 타임아웃');
      this._handleError(new Error('쿼리 처리 시간이 초과되었습니다'));
    }, QUERY_TIMEOUT);
  }

  _clearQueryTimeout() {
    if (this.queryTimeoutId) {
      clearTimeout(this.queryTimeoutId);
      this.queryTimeoutId = null;
    }
  }

  // ===== 연결 상태 (private) =====

  _updateConnectionState(state) {
    useReplayStore.getState().setConnectionState(state);
  }

  // ===== 정리 (private) =====

  /**
   * 전체 정리
   * 참조: mda-react-front TrackingWebSocketService.cleanup()
   */
  _cleanup() {
    this._clearQueryTimeout();
    this._clearSubscriptions();

    if (this.client?.connected) {
      try { this.client.deactivate(); } catch (e) { /* ignore */ }
    }

    this.client = null;
    this.currentQueryId = null;
    this.connectionPromise = null;
    this.isConnecting = false;

    this._updateConnectionState(ConnectionState.DISCONNECTED);
  }

  // ===== 지도 뷰포트 (private) =====

  /**
   * OpenLayers 맵에서 현재 뷰포트 좌표 추출
   * 참조: mda-react-front/src/tracking/components/ReplayV2.tsx getMapViewport()
   * @returns {{ minLon: number, maxLon: number, minLat: number, maxLat: number } | undefined}
   */
  _getMapViewport() {
    try {
      const map = window.__mainMap__;
      if (!map) {
        console.warn('[ReplayWS] 맵 인스턴스 없음 (window.__mainMap__)');
        return undefined;
      }

      const view = map.getView();
      const extent3857 = view.calculateExtent(map.getSize());
      const [minLon, minLat, maxLon, maxLat] = transformExtent(extent3857, 'EPSG:3857', 'EPSG:4326');

      return { minLon, maxLon, minLat, maxLat };
    } catch (error) {
      console.error('[ReplayWS] 뷰포트 추출 실패:', error);
      return undefined;
    }
  }

  /**
   * 현재 맵 줌 레벨
   */
  _getMapZoomLevel() {
    try {
      const map = window.__mainMap__;
      if (!map) return 10;
      return Math.round(map.getView().getZoom()) || 10;
    } catch {
      return 10;
    }
  }

  // ===== 유틸 (private) =====

  _normalizeChunkResponse(chunk) {
    return {
      queryId: chunk.queryId,
      chunkId: chunk.chunkId || `chunk_${chunk.chunkIndex}`,
      chunkIndex: chunk.chunkIndex,
      totalChunks: chunk.totalChunks,
      tracks: chunk.tracks,
      mergedTracks: chunk.mergedTracks,
      compactTracks: chunk.compactTracks,
      isLastChunk: chunk.isLastChunk || false,
      metadata: chunk.metadata,
    };
  }
}

// 싱글톤 인스턴스
let instance = null;

export function getReplayWebSocketService() {
  if (!instance) {
    instance = new ReplayWebSocketService();
  }
  return instance;
}

export default ReplayWebSocketService;
