/**
 * WebSocket connection manager for real-time collaboration
 * Handles connection lifecycle, reconnection, and authentication
 */

import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
import { PresenceManager, PresenceMessage } from './presence-manager';

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

export interface WebSocketConnectionOptions {
  serverUrl: string;
  documentName: string;
  authToken: string;
  reconnectInterval?: number;
  maxReconnectInterval?: number;
  doc?: Y.Doc;
}

export interface WebSocketConnectionManager {
  connect(): Promise<void>;
  disconnect(): void;
  getStatus(): ConnectionStatus;
  getProvider(): WebsocketProvider;
  onStatusChange(callback: (status: ConnectionStatus) => void): void;
  removeStatusListener(callback: (status: ConnectionStatus) => void): void;
}

/**
 * Extended WebSocket connection with presence support
 */
export interface WebSocketConnectionWithPresence extends WebSocketConnectionManager {
  getPresenceManager(): PresenceManager | null;
  initializePresence(userId: string, userName: string, userColor?: string): void;
  isPresenceInitialized(): boolean;
}

export class WebSocketConnection implements WebSocketConnectionWithPresence {
  private provider: WebsocketProvider | null = null;
  private status: ConnectionStatus = 'disconnected';
  private options: WebSocketConnectionOptions;
  private statusListeners: Set<(status: ConnectionStatus) => void> = new Set();
  private reconnectAttempts: number = 0;
  private currentReconnectInterval: number;
  
  // Presence management
  private presenceManager: PresenceManager | null = null;
  private presenceInitialized: boolean = false;

  constructor(options: WebSocketConnectionOptions) {
    this.options = options;
    this.currentReconnectInterval = options.reconnectInterval || 1000;
  }

  async connect(): Promise<void> {
    if (this.provider) {
      return;
    }

    this.updateStatus('connecting');

    try {
      // Create or use provided Yjs doc
      const ydoc = this.options.doc || new Y.Doc();
      
      // Create provider without auth token in URL (security: tokens in query params leak to logs)
      this.provider = new WebsocketProvider(
        this.options.serverUrl,
        this.options.documentName,
        ydoc,
        {
          connect: true,
          // Don't pass token in URL - send via awareness after connection
          maxBackoffTime: this.options.maxReconnectInterval || 30000,
        }
      );

      // Listen for provider status changes
      this.provider.on('status', (event: { status: string }) => {
        const newStatus = event.status as ConnectionStatus;
        this.updateStatus(newStatus);
        
        if (newStatus === 'connected') {
          this.reconnectAttempts = 0;
          this.currentReconnectInterval = this.options.reconnectInterval || 1000;
          // Send auth token via awareness state after connection (secure: not in URL logs)
          this.sendAuthToken();
        }
      });

        // Wait for initial connection
        if (this.provider.wsconnected) {
          this.updateStatus('connected');
        } else {
          // Wait for connection event
          await new Promise<void>((resolve, reject) => {
            const onConnect = (event: { status: string }) => {
              if (event.status === 'connected') {
                this.provider!.off('status', onConnect);
                resolve();
              }
            };
            const onError = (error: Error) => {
              this.provider!.off('status', onError);
              reject(error);
            };
            this.provider!.on('status', onConnect);
            this.provider!.on('status', onError);
            
            // Timeout after 30 seconds
            setTimeout(() => {
              this.provider?.off('status', onConnect);
              this.provider?.off('status', onError);
              reject(new Error('Connection timeout'));
            }, 30000);
          });
        }
    } catch (error) {
      console.error('Failed to connect to WebSocket server:', error);
      this.updateStatus('disconnected');
      throw error;
    }
  }

  disconnect(): void {
    if (this.provider) {
      this.provider.destroy();
      this.provider = null;
      this.updateStatus('disconnected');
    }
  }

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

  getProvider(): WebsocketProvider {
    if (!this.provider) {
      throw new Error('WebSocket provider not initialized. Call connect() first.');
    }
    return this.provider;
  }

  onStatusChange(callback: (status: ConnectionStatus) => void): void {
    this.statusListeners.add(callback);
  }

  removeStatusListener(callback: (status: ConnectionStatus) => void): void {
    this.statusListeners.delete(callback);
  }

  private updateStatus(newStatus: ConnectionStatus): void {
    const oldStatus = this.status;
    this.status = newStatus;
    
    console.log(`Connection status: ${oldStatus} → ${newStatus}`);
    
    // Notify listeners
    this.statusListeners.forEach(listener => listener(newStatus));
    
    // Handle reconnection logic
    if (newStatus === 'disconnected' && oldStatus === 'connected') {
      this.scheduleReconnect();
    }
  }

  private scheduleReconnect(): void {
    const delay = Math.min(
      this.currentReconnectInterval,
      this.options.maxReconnectInterval || 30000
    );

    console.log(`Scheduling reconnection in ${delay}ms (attempt ${this.reconnectAttempts + 1})`);

    setTimeout(async () => {
      try {
        this.updateStatus('reconnecting');
        await this.connect();
      } catch (error) {
        console.error('Reconnection failed:', error);
        this.reconnectAttempts++;
        // Exponential backoff
        this.currentReconnectInterval *= 2;
        this.scheduleReconnect();
      }
    }, delay);
  }

  /**
   * Send auth token via awareness state after connection
   * Security: Only send userId and projectId (not full JWT) to avoid credential broadcast
   */
  private sendAuthToken(): void {
    if (!this.provider || !this.provider.awareness) {
      console.error('[WebSocketConnection] Cannot send auth token: provider not ready');
      return;
    }
    
    // Parse JWT to extract userId and projectId (don't broadcast full token)
    try {
      const jwtPayload = this.options.authToken.split('.')[1];
      const payload = JSON.parse(atob(jwtPayload || ''));
      this.provider.awareness.setLocalStateField('auth', {
        userId: payload.userId,
        projectId: payload.projectId,
        timestamp: Date.now(),
      });
      console.log('[WebSocketConnection] Auth credentials sent via awareness (userId, projectId only)');
    } catch (error) {
      console.error('[WebSocketConnection] Failed to parse JWT token:', error);
    }
  }

  /**
   * Get the presence manager instance
   */
  getPresenceManager(): PresenceManager | null {
    return this.presenceManager;
  }

  /**
   * Initialize presence tracking for this connection
   */
  initializePresence(userId: string, userName: string, userColor?: string): void {
    if (this.presenceInitialized) {
      console.log('[WebSocketConnection] Presence already initialized');
      return;
    }

    // Import DEFAULT_USER_COLORS here to avoid circular dependency
    const { DEFAULT_USER_COLORS } = require('./presence-manager');
    const color = userColor || DEFAULT_USER_COLORS[Math.abs(userId.charCodeAt(0)) % DEFAULT_USER_COLORS.length];
    
    this.presenceManager = new PresenceManager({
      userId,
      userName,
      userColor: color,
    });
    
    // Initialize presence with this connection
    this.presenceManager.initialize(this);
    this.presenceInitialized = true;
    
    console.log(`[WebSocketConnection] Presence initialized for ${userName} (${userId}) with color ${color}`);
  }

  /**
   * Check if presence has been initialized
   */
  isPresenceInitialized(): boolean {
    return this.presenceInitialized;
  }
}
