/**
 * Market WebSocket Service
 * Spring Boot Backend STOMP WebSocket 연결 및 실시간 캔들 데이터 수신
 */
import SockJS from 'sockjs-client';
import { Client, IMessage, StompSubscription } from '@stomp/stompjs';
import { getApiBaseUrl } from './apiConfig';

const API_BASE_URL = getApiBaseUrl();

// SockJS는 HTTP/HTTPS URL을 받아서 내부적으로 WebSocket으로 변환
// 따라서 http:// 또는 https:// 프로토콜을 사용해야 함
const getWebSocketUrl = (): string => {
  // API_BASE_URL에서 /api를 제거하고 /ws 추가
  // 예: http://localhost:8083/api → http://localhost:8083/ws
  const baseUrl = API_BASE_URL.replace('/api', '');
  return `${baseUrl}/ws`;
};

const WS_URL = getWebSocketUrl();

export interface RealtimeCandleUpdate {
  market: string;
  interval: string;
  candles: CandleUpdateData[];
  timestamp: number;
  type: 'snapshot' | 'update';
}

export interface CandleUpdateData {
  time: string;  // "2026-03-07T01:12"
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
  isBuyPoint?: boolean;
  isSellPoint?: boolean;
  crossState?: string | null;
}

type CandleUpdateCallback = (update: RealtimeCandleUpdate) => void;
type ConnectionStatusCallback = (connected: boolean) => void;
type ErrorCallback = (error: Error) => void;

interface Subscription {
  market: string;
  interval: string;
  callback: CandleUpdateCallback;
  stompSubscription?: StompSubscription;
}

/**
 * Market WebSocket Client
 * STOMP over SockJS를 사용한 실시간 캔들 데이터 구독
 */
class MarketSocketClient {
  private client: Client | null = null;
  private subscriptions: Map<string, Subscription> = new Map();
  private connectionStatusCallbacks: Set<ConnectionStatusCallback> = new Set();
  private errorCallbacks: Set<ErrorCallback> = new Set();
  private isConnecting = false;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 10;
  private reconnectDelay = 3000;
  private reconnectTimer: NodeJS.Timeout | null = null;

  constructor() {
    console.log('[MarketSocket] Client initialized');
  }

  /**
   * WebSocket 연결
   */
  connect(): Promise<void> {
    if (this.client?.connected) {
      console.log('[MarketSocket] Already connected');
      return Promise.resolve();
    }

    if (this.isConnecting) {
      console.log('[MarketSocket] Connection in progress');
      return Promise.resolve();
    }

    return new Promise((resolve, reject) => {
      try {
        this.isConnecting = true;

        // SockJS 소켓 생성
        const socket = new SockJS(WS_URL);

        // STOMP 클라이언트 생성
        this.client = new Client({
          webSocketFactory: () => socket as any,
          debug: (str) => {
            if (process.env.NODE_ENV === 'development') {
              console.log('[STOMP Debug]', str);
            }
          },
          reconnectDelay: this.reconnectDelay,
          heartbeatIncoming: 10000,
          heartbeatOutgoing: 10000,
          onConnect: () => {
            console.log('✅ [MarketSocket] Connected to WebSocket');
            this.isConnecting = false;
            this.reconnectAttempts = 0;
            this.notifyConnectionStatus(true);
            this.resubscribeAll();
            resolve();
          },
          onDisconnect: () => {
            console.log('🔌 [MarketSocket] Disconnected from WebSocket');
            this.isConnecting = false;
            this.notifyConnectionStatus(false);
          },
          onStompError: (frame) => {
            console.error('❌ [MarketSocket] STOMP error:', frame.headers['message']);
            this.isConnecting = false;
            this.notifyError(new Error(frame.headers['message'] || 'STOMP error'));
            this.handleReconnect();
            reject(new Error(frame.headers['message']));
          },
          onWebSocketError: (event) => {
            console.error('❌ [MarketSocket] WebSocket error:', event);
            this.isConnecting = false;
            this.notifyError(new Error('WebSocket connection error'));
            this.handleReconnect();
            reject(new Error('WebSocket connection error'));
          },
        });

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

      } catch (error) {
        console.error('❌ [MarketSocket] Failed to connect:', error);
        this.isConnecting = false;
        this.notifyError(error as Error);
        reject(error);
      }
    });
  }

  /**
   * WebSocket 연결 해제
   */
  disconnect(): void {
    console.log('[MarketSocket] Disconnecting...');
    
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }

    this.subscriptions.clear();
    
    if (this.client?.connected) {
      this.client.deactivate();
    }
    
    this.client = null;
    this.notifyConnectionStatus(false);
  }

  /**
   * 자동 재연결 처리
   */
  private handleReconnect(): void {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('[MarketSocket] Max reconnect attempts reached');
      return;
    }

    if (this.reconnectTimer) {
      return;
    }

    this.reconnectAttempts++;
    const delay = this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1);
    
    console.log(`[MarketSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
    
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = null;
      this.connect().catch((error) => {
        console.error('[MarketSocket] Reconnect failed:', error);
      });
    }, delay);
  }

  /**
   * 특정 마켓/타임프레임 구독
   * 
   * @param market 마켓 코드 (예: KRW-BTC)
   * @param interval 타임프레임 (예: 1m, 5m, 10m)
   * @param callback 캔들 업데이트 콜백
   * @returns 구독 해제 함수
   */
  subscribe(
    market: string,
    interval: string,
    callback: CandleUpdateCallback
  ): () => void {
    const key = `${market}:${interval}`;
    
    console.log(`[MarketSocket] Subscribing to ${key}`);

    // 기존 구독 해제
    if (this.subscriptions.has(key)) {
      this.unsubscribe(market, interval);
    }

    // 구독 정보 저장
    const subscription: Subscription = {
      market,
      interval,
      callback,
    };

    this.subscriptions.set(key, subscription);

    // 연결되어 있으면 즉시 구독
    if (this.client?.connected) {
      this.subscribeToTopic(subscription);
    } else {
      // 연결되지 않았으면 연결 시도
      console.log('[MarketSocket] Not connected, connecting...');
      this.connect().catch((error) => {
        console.error('[MarketSocket] Failed to connect for subscription:', error);
      });
    }

    // 구독 해제 함수 반환
    return () => this.unsubscribe(market, interval);
  }

  /**
   * STOMP 토픽 구독
   */
  private subscribeToTopic(subscription: Subscription): void {
    if (!this.client?.connected) {
      console.warn('[MarketSocket] Cannot subscribe: not connected');
      return;
    }

    const { market, interval, callback } = subscription;
    const topic = `/topic/candles/${market}/${interval}`;
    const key = `${market}:${interval}`;

    try {
      const stompSubscription = this.client.subscribe(topic, (message: IMessage) => {
        try {
          const update: RealtimeCandleUpdate = JSON.parse(message.body);
          if (typeof window !== 'undefined') {
            (window as any).__lastRealtimeUpdate = { raw: message.body, parsed: update, receivedAt: Date.now() };
          }
          console.log(`📊 [MarketSocket] Received update for ${market} ${interval}:`, {
            type: update.type,
            candleCount: update.candles?.length || 0,
            timestamp: update.timestamp
          });

          callback(update);
        } catch (error) {
          console.error('[MarketSocket] Failed to parse message:', error);
          this.notifyError(error as Error);
        }
      });

      // STOMP 구독 객체 저장
      const sub = this.subscriptions.get(key);
      if (sub) {
        sub.stompSubscription = stompSubscription;
      }

      console.log(`✅ [MarketSocket] Subscribed to ${topic}`);

    } catch (error) {
      console.error(`[MarketSocket] Failed to subscribe to ${topic}:`, error);
      this.notifyError(error as Error);
    }
  }

  /**
   * 구독 해제
   */
  unsubscribe(market: string, interval: string): void {
    const key = `${market}:${interval}`;
    const subscription = this.subscriptions.get(key);

    if (!subscription) {
      return;
    }

    console.log(`[MarketSocket] Unsubscribing from ${key}`);

    // STOMP 구독 해제
    if (subscription.stompSubscription) {
      try {
        subscription.stompSubscription.unsubscribe();
      } catch (error) {
        console.error('[MarketSocket] Failed to unsubscribe:', error);
      }
    }

    this.subscriptions.delete(key);
  }

  /**
   * 모든 구독 재구독 (재연결 시)
   */
  private resubscribeAll(): void {
    console.log(`[MarketSocket] Resubscribing to ${this.subscriptions.size} subscriptions`);
    
    this.subscriptions.forEach((subscription) => {
      this.subscribeToTopic(subscription);
    });
  }

  /**
   * 연결 상태 콜백 등록
   */
  onConnectionStatusChange(callback: ConnectionStatusCallback): () => void {
    this.connectionStatusCallbacks.add(callback);
    
    // 현재 상태 즉시 전달
    if (this.client?.connected) {
      callback(true);
    }
    
    return () => this.connectionStatusCallbacks.delete(callback);
  }

  /**
   * 에러 콜백 등록
   */
  onError(callback: ErrorCallback): () => void {
    this.errorCallbacks.add(callback);
    return () => this.errorCallbacks.delete(callback);
  }

  /**
   * 연결 상태 알림
   */
  private notifyConnectionStatus(connected: boolean): void {
    this.connectionStatusCallbacks.forEach((callback) => {
      try {
        callback(connected);
      } catch (error) {
        console.error('[MarketSocket] Error in connection status callback:', error);
      }
    });
  }

  /**
   * 에러 알림
   */
  private notifyError(error: Error): void {
    this.errorCallbacks.forEach((callback) => {
      try {
        callback(error);
      } catch (err) {
        console.error('[MarketSocket] Error in error callback:', err);
      }
    });
  }

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

  /**
   * 활성 구독 수
   */
  getSubscriptionCount(): number {
    return this.subscriptions.size;
  }
}

// 전역 싱글톤 인스턴스
export const marketSocket = new MarketSocketClient();

// 개발 환경에서 디버깅용
if (process.env.NODE_ENV === 'development') {
  (window as any).marketSocket = marketSocket;
}
