/**
 * WebSocket service for real-time blockchain updates.
 * Handles connection management, reconnection, and event subscriptions.
 */

export type WebSocketStatus = 'connecting' | 'connected' | 'disconnected' | 'reconnecting';

export interface BlockEvent {
  type: 'new_block';
  hash: string;
  blueScore: number;
  timestamp: number;
  txCount: number;
  isChainBlock: boolean;
}

export interface StatsEvent {
  type: 'stats_update';
  blockCount: number;
  virtualDaaScore: number;
  difficulty: number;
  mempoolSize: number;
  hashrate: number;
  hashrateHuman: string;
}

export interface TipEvent {
  type: 'tip_update';
  tips: string[];
  tipCount: number;
}

export interface MempoolEvent {
  type: 'mempool_tx';
  txId: string;
  fee: number;
  mass: number;
}

export type WebSocketEvent = BlockEvent | StatsEvent | TipEvent | MempoolEvent;

type EventCallback<T extends WebSocketEvent = WebSocketEvent> = (event: T) => void;
type StatusCallback = (status: WebSocketStatus) => void;

class WebSocketService {
  private ws: WebSocket | null = null;
  private url: string;
  private subscriptions: Map<string, Set<EventCallback>> = new Map();
  private statusListeners: Set<StatusCallback> = new Set();
  private status: WebSocketStatus = 'disconnected';
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 10;
  private reconnectDelay = 1000;
  private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
  private mockMode = false;
  private mockInterval: ReturnType<typeof setInterval> | null = null;

  constructor() {
    // Default WebSocket URL - can be overridden
    const wsProtocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsHost = typeof window !== 'undefined' ? window.location.host : 'localhost:3000';
    this.url = `${wsProtocol}//${wsHost}/ws`;

    // Check if mock mode should be enabled
    this.mockMode = this.shouldUseMock();
  }

  private shouldUseMock(): boolean {
    if (typeof window !== 'undefined') {
      if (localStorage.getItem('useMockApi') === 'true') return true;
    }
    return import.meta.env.VITE_USE_MOCK === 'true';
  }

  setUrl(url: string) {
    this.url = url;
  }

  enableMock(enable: boolean) {
    this.mockMode = enable;
    if (enable && this.status === 'connected') {
      this.startMockUpdates();
    } else if (!enable) {
      this.stopMockUpdates();
    }
  }

  connect(): void {
    if (this.mockMode) {
      this.setStatus('connected');
      this.startMockUpdates();
      return;
    }

    if (this.ws?.readyState === WebSocket.OPEN) {
      return;
    }

    this.setStatus('connecting');

    try {
      this.ws = new WebSocket(this.url);

      this.ws.onopen = () => {
        this.setStatus('connected');
        this.reconnectAttempts = 0;
        this.reconnectDelay = 1000;
        console.log('[WS] Connected to', this.url);
      };

      this.ws.onmessage = (event) => {
        try {
          const data = JSON.parse(event.data) as WebSocketEvent;
          this.emit(data);
        } catch (e) {
          console.error('[WS] Failed to parse message:', e);
        }
      };

      this.ws.onerror = (error) => {
        console.error('[WS] Error:', error);
      };

      this.ws.onclose = (event) => {
        console.log('[WS] Disconnected:', event.code, event.reason);
        this.setStatus('disconnected');
        this.scheduleReconnect();
      };
    } catch (error) {
      console.error('[WS] Failed to connect:', error);
      this.setStatus('disconnected');
      this.scheduleReconnect();
    }
  }

  disconnect(): void {
    this.stopMockUpdates();
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    if (this.ws) {
      this.ws.close(1000, 'Client disconnect');
      this.ws = null;
    }
    this.setStatus('disconnected');
  }

  private scheduleReconnect(): void {
    if (this.mockMode) return;
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.log('[WS] Max reconnect attempts reached');
      return;
    }

    this.reconnectAttempts++;
    this.setStatus('reconnecting');

    const delay = Math.min(this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1), 30000);
    console.log(`[WS] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);

    this.reconnectTimer = setTimeout(() => {
      this.connect();
    }, delay);
  }

  private setStatus(status: WebSocketStatus): void {
    this.status = status;
    this.statusListeners.forEach(callback => callback(status));
  }

  getStatus(): WebSocketStatus {
    return this.status;
  }

  onStatusChange(callback: StatusCallback): () => void {
    this.statusListeners.add(callback);
    return () => this.statusListeners.delete(callback);
  }

  subscribe<T extends WebSocketEvent>(eventType: T['type'], callback: EventCallback<T>): () => void {
    if (!this.subscriptions.has(eventType)) {
      this.subscriptions.set(eventType, new Set());
    }
    this.subscriptions.get(eventType)!.add(callback as EventCallback);

    // Return unsubscribe function
    return () => {
      this.subscriptions.get(eventType)?.delete(callback as EventCallback);
    };
  }

  private emit(event: WebSocketEvent): void {
    const callbacks = this.subscriptions.get(event.type);
    if (callbacks) {
      callbacks.forEach(callback => callback(event));
    }

    // Also emit to wildcard subscribers
    const wildcardCallbacks = this.subscriptions.get('*');
    if (wildcardCallbacks) {
      wildcardCallbacks.forEach(callback => callback(event));
    }
  }

  // Mock data simulation for development
  private startMockUpdates(): void {
    if (this.mockInterval) return;

    let blockCounter = 125847;
    let mempoolSize = 42;

    this.mockInterval = setInterval(() => {
      // Simulate new block every 1 second
      blockCounter++;
      const blockEvent: BlockEvent = {
        type: 'new_block',
        hash: this.generateMockHash(blockCounter),
        blueScore: blockCounter,
        timestamp: Date.now(),
        txCount: Math.floor(Math.random() * 50) + 1,
        isChainBlock: true,
      };
      this.emit(blockEvent);

      // Stats update with each block
      mempoolSize = Math.max(0, mempoolSize + Math.floor(Math.random() * 10) - 5);
      const statsEvent: StatsEvent = {
        type: 'stats_update',
        blockCount: blockCounter,
        virtualDaaScore: blockCounter,
        difficulty: 1234567890 + Math.random() * 100000000,
        mempoolSize,
        hashrate: 45.6e9 + Math.random() * 5e9,
        hashrateHuman: `${(45.6 + Math.random() * 5).toFixed(2)} GH/s`,
      };
      this.emit(statsEvent);

      // Occasional tip updates
      if (Math.random() > 0.7) {
        const tipEvent: TipEvent = {
          type: 'tip_update',
          tips: [
            this.generateMockHash(blockCounter),
            this.generateMockHash(blockCounter - 1),
            this.generateMockHash(blockCounter - 2),
          ],
          tipCount: 3,
        };
        this.emit(tipEvent);
      }
    }, 1000);
  }

  private stopMockUpdates(): void {
    if (this.mockInterval) {
      clearInterval(this.mockInterval);
      this.mockInterval = null;
    }
  }

  private generateMockHash(seed: number): string {
    const chars = '0123456789abcdef';
    let hash = '';
    for (let i = 0; i < 64; i++) {
      const charIndex = (seed * 31 + i * 7 + Math.floor(i / 8) * seed) % 16;
      hash += chars[Math.abs(charIndex)];
    }
    return hash;
  }
}

// Singleton instance
export const wsService = new WebSocketService();
export default wsService;
