import * as Ably from 'ably';

type ActionHandlerCallback = (actionType: string | undefined | null, messageData: any) => void | Promise<void>;

export class ClientAblyService {
  private static client: Ably.Realtime | null = null;
  private static channel: Ably.RealtimeChannel | null = null;
  private static isConnected: boolean = false;
  private static sessionId: string | null = null;
  private static messageUnsubscribe: (() => void) | null = null;
  private static onActionReceived: ActionHandlerCallback | null = null;

  static async startConnection(
    sessionId: string,
    ablyToken: string,
    onMessageReceived: Function,
    tenantId: string,
    onActionReceived?: ActionHandlerCallback
  ) {
    // Prevent multiple connections
    if (this.isConnected && this.sessionId === sessionId) {
      return;
    }

    // Close existing connection if connecting to a different session
    if (this.isConnected && this.sessionId !== sessionId) {
      await this.stopConnection();
    }

    try {
      // Initialize Ably client with the token
      this.client = new Ably.Realtime({
        authUrl: undefined,
        token: ablyToken,
        autoConnect: true,
      });

      this.client.connection.on('failed', (stateChange) => {
        console.error('[AblyService] Connection state: failed', {
          reason: stateChange.reason?.message,
          error: stateChange.reason,
        });
      });

      // Wait for connection to be established
      await new Promise<void>((resolve, reject) => {
        if (!this.client) {
          const error = new Error('Failed to initialize Ably client');
          console.error('[AblyService]', error);
          reject(error);
          return;
        }

        this.client.connection.once('connected', () => {
          this.isConnected = true;
          this.sessionId = sessionId;
          resolve();
        });

        this.client.connection.once('failed', (stateChange) => {
          const error = new Error(`Ably connection failed: ${stateChange.reason?.message || 'Unknown error'}`);
          console.error('[AblyService] Connection failed', {
            reason: stateChange.reason?.message,
            error: stateChange.reason,
          });
          reject(error);
        });

        this.client.connection.once('disconnected', (stateChange) => {
          const error = new Error(`Ably connection disconnected: ${stateChange.reason?.message || 'Unknown error'}`);
          console.error('[AblyService] Connection disconnected', { reason: stateChange.reason?.message });
          reject(error);
        });

        // Set a timeout for connection
        setTimeout(() => {
          if (!this.isConnected) {
            const error = new Error('Ably connection timeout');
            console.error('[AblyService] Connection timeout after 10 seconds');
            reject(error);
          }
        }, 10000);
      });

      // Store optional action handler for this connection
      this.onActionReceived = onActionReceived ?? null;

      // Subscribe to the session room
      await this.joinChannel(sessionId, onMessageReceived, tenantId);
    } catch (error) {
      console.error('[AblyService] Error in startConnection', { error, sessionId });
      this.isConnected = false;
      this.sessionId = null;
      throw error;
    }
  }

  private static async joinChannel(sessionId: string, onMessageReceived: Function, tenantId: string) {
    if (!this.client) {
      const error = new Error('Chat client not initialized');
      console.error('[AblyService] joinChannel error:', error);
      throw error;
    }

    const roomName = `session:${tenantId}:${sessionId}`;

    // Set up raw channel subscription for server messages
    if (this.client) {
      this.channel = this.client.channels.get(roomName);

      this.channel.on('failed', (stateChange) => {
        console.error('[AblyService] Channel failed', {
          roomName,
          reason: stateChange.reason?.message,
          error: stateChange.reason,
        });
      });

      // Subscribe to assistant/system responses
      this.channel.subscribe('ReceiveMessage', (message) => {
        try {
          // Ensure messageContent is always a string (default to empty string if undefined)
          const rawData = message.data;

          const messageContent =
            typeof rawData === 'string' ? rawData : rawData?.content ?? rawData?.message ?? '';
          const senderType = rawData?.senderType || 3; // Assistant
          const needsAgent = rawData?.needsAgent || rawData?.actionType == 'needs_agent' || false;
          const attachments = rawData?.attachments || [];
          const actionType = (rawData && typeof rawData.actionType === 'string' ? rawData.actionType : '') as
            | string
            | undefined
            | null;

          // Extract downloadUrl from attachments (Ably now returns downloadUrl directly)
          // Attachments can be: strings (URLs), objects with downloadUrl, url, or id only
          const attachmentUrls: string[] = [];
          const attachmentIdsFromAttachments: string[] = [];
          for (const attachment of attachments) {
            if (typeof attachment === 'string') {
              if (attachment.startsWith('http://') || attachment.startsWith('https://')) {
                attachmentUrls.push(attachment);
              } else {
                attachmentIdsFromAttachments.push(attachment);
              }
            } else if (attachment?.downloadUrl) {
              attachmentUrls.push(attachment.downloadUrl);
            } else if (attachment?.url) {
              attachmentUrls.push(attachment.url);
            } else if (attachment?.id && typeof attachment.id === 'string') {
              attachmentIdsFromAttachments.push(attachment.id);
            }
          }
          const attachmentIds =
            attachmentIdsFromAttachments.length > 0
              ? attachmentIdsFromAttachments
              : (rawData?.attachmentIds as string[] | undefined) || [];

          // Invoke optional action handler first (non-blocking for message processing)
          if (this.onActionReceived && actionType !== undefined) {
            try {
              void this.onActionReceived(actionType, rawData);
            } catch (actionError) {
              console.error('[AblyService] Error in action handler callback', {
                error: actionError,
                actionType,
                rawData,
              });
            }
          }

          onMessageReceived(messageContent, senderType, needsAgent, attachmentUrls, attachmentIds);
        } catch (error) {
          console.error('[AblyService] Error processing message', { error, message });
        }
      });

      await this.channel.attach();
    }
  }

  static async stopConnection() {
    try {
      // Unsubscribe from room messages
      if (this.messageUnsubscribe) {
        this.messageUnsubscribe();
        this.messageUnsubscribe = null;
      }

      // Unsubscribe and detach from raw channel
      if (this.channel) {
        this.channel.unsubscribe();
        await this.channel.detach();
        this.channel = null;
      }

      // Close Ably connection
      if (this.client) {
        this.client.close();
        this.client = null;
      }

      this.isConnected = false;
      this.sessionId = null;
      this.onActionReceived = null;
    } catch (error) {
      console.error('[AblyService] Error in stopConnection', { error });
      // Reset state even if there's an error
      this.isConnected = false;
      this.sessionId = null;
      this.client = null;
      this.channel = null;
      this.messageUnsubscribe = null;
    }
  }

  static isConnectionActive(): boolean {
    return this.isConnected && this.client?.connection.state === 'connected';
  }

  static getConnectionState(): string {
    return this.client?.connection.state || 'disconnected';
  }

  // Method to manually send a message (if needed for debugging or direct messaging)
  static async sendMessage(messageContent: string, senderType: number = 1) {
    if (!this.channel || !this.isConnected) {
      throw new Error('Connection not active');
    }

    try {
      await this.channel.publish('message', {
        text: messageContent,
        metadata: {
          senderType,
          sentAt: new Date().toISOString(),
        },
      });
    } catch (error) {
      throw error;
    }
  }
}
