import {
  SIGNALING_TYPE_JOIN_SERVER,
  SIGNALING_TYPE_LEAVE_SERVER,
  SIGNALING_TYPE_VIEW_SERVER
} from '../realtime.constants';
import { ServerSignalingCoordinator } from './server-signaling-coordinator';
import { SignalingTransportHandler } from './signaling-transport-handler';
import { WebRTCLogger } from '../logging/webrtc-logger';

interface ServerMembershipSignalingHandlerDependencies<TMessage> {
  signalingCoordinator: ServerSignalingCoordinator<TMessage>;
  signalingTransport: SignalingTransportHandler<TMessage>;
  logger: WebRTCLogger;
  getActiveServerId(): string | null;
  isVoiceConnected(): boolean;
  runFullCleanup(): void;
}

export class ServerMembershipSignalingHandler<TMessage> {
  constructor(
    private readonly dependencies: ServerMembershipSignalingHandlerDependencies<TMessage>
  ) {}

  getCurrentSignalingUrl(): string | null {
    return this.dependencies.signalingTransport.getCurrentSignalingUrl(this.dependencies.getActiveServerId());
  }

  joinRoom(roomId: string, userId: string, signalUrl?: string): void {
    const resolvedSignalUrl = this.resolveSignalUrl(roomId, signalUrl);

    if (!resolvedSignalUrl) {
      this.dependencies.logger.warn('[signaling] Cannot join room without a signaling URL', { roomId });
      return;
    }

    this.migrateServerSignalUrl(roomId, resolvedSignalUrl);

    this.dependencies.signalingCoordinator.setServerSignalUrl(roomId, resolvedSignalUrl);
    this.dependencies.signalingCoordinator.setLastJoinedServer(resolvedSignalUrl, {
      serverId: roomId,
      userId
    });

    this.dependencies.signalingCoordinator.addJoinedServer(resolvedSignalUrl, roomId);

    this.dependencies.signalingTransport.sendRawMessageToSignalUrl(resolvedSignalUrl, {
      type: SIGNALING_TYPE_JOIN_SERVER,
      serverId: roomId
    });
  }

  switchServer(serverId: string, userId: string, signalUrl?: string): void {
    const resolvedSignalUrl = this.resolveSignalUrl(serverId, signalUrl);

    if (!resolvedSignalUrl) {
      this.dependencies.logger.warn('[signaling] Cannot switch server without a signaling URL', { serverId });
      return;
    }

    this.migrateServerSignalUrl(serverId, resolvedSignalUrl);

    this.dependencies.signalingCoordinator.setServerSignalUrl(serverId, resolvedSignalUrl);
    this.dependencies.signalingCoordinator.setLastJoinedServer(resolvedSignalUrl, {
      serverId,
      userId
    });

    const memberServerIds = this.dependencies.signalingCoordinator.getMemberServerIdsForSignalUrl(resolvedSignalUrl);

    if (memberServerIds.has(serverId)) {
      this.dependencies.signalingTransport.sendRawMessageToSignalUrl(resolvedSignalUrl, {
        type: SIGNALING_TYPE_VIEW_SERVER,
        serverId
      });

      this.dependencies.logger.info('Viewed server (already joined)', {
        serverId,
        signalUrl: resolvedSignalUrl,
        userId,
        voiceConnected: this.dependencies.isVoiceConnected()
      });

      return;
    }

    this.dependencies.signalingCoordinator.addJoinedServer(resolvedSignalUrl, serverId);
    this.dependencies.signalingTransport.sendRawMessageToSignalUrl(resolvedSignalUrl, {
      type: SIGNALING_TYPE_JOIN_SERVER,
      serverId
    });

    this.dependencies.logger.info('Joined new server via switch', {
      serverId,
      signalUrl: resolvedSignalUrl,
      userId,
      voiceConnected: this.dependencies.isVoiceConnected()
    });
  }

  leaveRoom(serverId?: string): void {
    if (serverId) {
      this.leaveSingleRoom(serverId);
      return;
    }

    const joinedEntries = this.dependencies.signalingCoordinator.getJoinedServerEntries();

    for (const { signalUrl, serverIds } of joinedEntries) {
      for (const joinedServerId of serverIds) {
        this.dependencies.signalingTransport.sendRawMessageToSignalUrl(signalUrl, {
          type: SIGNALING_TYPE_LEAVE_SERVER,
          serverId: joinedServerId
        });
      }
    }

    this.dependencies.signalingCoordinator.clearJoinedServers();

    for (const { signalUrl } of joinedEntries) {
      this.dependencies.signalingCoordinator.pruneUnusedSignalUrl(signalUrl);
    }

    this.dependencies.runFullCleanup();
  }

  private leaveSingleRoom(serverId: string): void {
    const resolvedSignalUrl = this.dependencies.signalingCoordinator.getServerSignalUrl(serverId);

    if (resolvedSignalUrl) {
      this.dependencies.signalingTransport.sendRawMessageToSignalUrl(resolvedSignalUrl, {
        type: SIGNALING_TYPE_LEAVE_SERVER,
        serverId
      });

      this.dependencies.signalingCoordinator.removeJoinedServer(resolvedSignalUrl, serverId);
      this.dependencies.signalingCoordinator.pruneUnusedSignalUrl(resolvedSignalUrl);
    } else {
      this.dependencies.signalingTransport.sendRawMessage({
        type: SIGNALING_TYPE_LEAVE_SERVER,
        serverId
      });

      this.dependencies.signalingCoordinator.removeJoinedServerEverywhere(serverId);
    }

    this.dependencies.signalingCoordinator.deleteServerSignalUrl(serverId);
    this.dependencies.logger.info('Left server', { serverId });

    if (this.dependencies.signalingCoordinator.getJoinedServerCount() === 0) {
      this.dependencies.runFullCleanup();
    }
  }

  private resolveSignalUrl(serverId: string, signalUrl?: string): string | null {
    return signalUrl
      ?? this.dependencies.signalingCoordinator.getServerSignalUrl(serverId)
      ?? this.getCurrentSignalingUrl();
  }

  private migrateServerSignalUrl(serverId: string, nextSignalUrl: string): void {
    const previousSignalUrl = this.dependencies.signalingCoordinator.getServerSignalUrl(serverId);

    if (!previousSignalUrl || previousSignalUrl === nextSignalUrl) {
      return;
    }

    this.dependencies.signalingTransport.sendRawMessageToSignalUrl(previousSignalUrl, {
      type: SIGNALING_TYPE_LEAVE_SERVER,
      serverId
    });

    this.dependencies.signalingCoordinator.removeJoinedServer(previousSignalUrl, serverId);
    this.dependencies.signalingCoordinator.pruneUnusedSignalUrl(previousSignalUrl);

    this.dependencies.logger.info('Migrated server to a new signaling route', {
      previousSignalUrl,
      serverId,
      signalUrl: nextSignalUrl
    });
  }
}
