import { Observable, of } from 'rxjs';
import type { SignalingMessage } from '../../../shared-kernel';
import { DEFAULT_DISPLAY_NAME, SIGNALING_TYPE_IDENTIFY } from '../realtime.constants';
import { IdentifyCredentials } from '../realtime.types';
import { ConnectedSignalingManager, ServerSignalingCoordinator } from './server-signaling-coordinator';
import { WebRTCLogger } from '../logging/webrtc-logger';

interface SignalingTransportHandlerDependencies<TMessage> {
  signalingCoordinator: ServerSignalingCoordinator<TMessage>;
  logger: WebRTCLogger;
  getLocalPeerId(): string;
}

export class SignalingTransportHandler<TMessage> {
  private lastIdentifyCredentials: IdentifyCredentials | null = null;

  constructor(
    private readonly dependencies: SignalingTransportHandlerDependencies<TMessage>
  ) {}

  getIdentifyCredentials(): IdentifyCredentials | null {
    return this.lastIdentifyCredentials;
  }

  getIdentifyOderId(): string {
    return this.lastIdentifyCredentials?.oderId || this.dependencies.getLocalPeerId();
  }

  getIdentifyDisplayName(): string {
    return this.lastIdentifyCredentials?.displayName || DEFAULT_DISPLAY_NAME;
  }

  getIdentifyDescription(): string | undefined {
    return this.lastIdentifyCredentials?.description;
  }

  getConnectedSignalingManagers(): ConnectedSignalingManager[] {
    return this.dependencies.signalingCoordinator.getConnectedSignalingManagers();
  }

  getCurrentSignalingUrl(activeServerId: string | null): string | null {
    if (activeServerId) {
      const activeServerSignalUrl = this.dependencies.signalingCoordinator.getServerSignalUrl(activeServerId);

      if (activeServerSignalUrl) {
        return activeServerSignalUrl;
      }
    }

    return this.getConnectedSignalingManagers()[0]?.signalUrl ?? null;
  }

  connectToSignalingServer(serverUrl: string): Observable<boolean> {
    const manager = this.dependencies.signalingCoordinator.ensureSignalingManager(serverUrl);

    if (manager.isSocketOpen()) {
      return of(true);
    }

    return manager.connect(serverUrl);
  }

  isSignalingConnectedTo(serverUrl: string): boolean {
    return this.dependencies.signalingCoordinator.isSignalingConnectedTo(serverUrl);
  }

  async ensureSignalingConnected(timeoutMs?: number): Promise<boolean> {
    return await this.dependencies.signalingCoordinator.ensureAnySignalingConnected(timeoutMs);
  }

  sendSignalingMessage(message: Omit<SignalingMessage, 'from' | 'timestamp'>): void {
    const targetPeerId = message.to;

    if (targetPeerId) {
      const targetSignalUrl = this.dependencies.signalingCoordinator.getPeerSignalUrl(targetPeerId);

      if (targetSignalUrl) {
        const targetManager = this.dependencies.signalingCoordinator.ensureSignalingManager(targetSignalUrl);

        targetManager.sendSignalingMessage(message, this.dependencies.getLocalPeerId());
        return;
      }
    }

    const connectedManagers = this.getConnectedSignalingManagers();

    if (connectedManagers.length === 0) {
      this.dependencies.logger.error('[signaling] No active signaling connection for outbound message', new Error('No signaling manager available'), {
        type: message.type
      });

      return;
    }

    for (const { manager } of connectedManagers) {
      manager.sendSignalingMessage(message, this.dependencies.getLocalPeerId());
    }
  }

  sendRawMessage(message: Record<string, unknown>): void {
    const targetPeerId = typeof message['targetUserId'] === 'string' ? message['targetUserId'] : null;
    const messageType = typeof message['type'] === 'string' ? message['type'] : 'unknown';

    if (targetPeerId) {
      const targetSignalUrl = this.dependencies.signalingCoordinator.getPeerSignalUrl(targetPeerId);

      if (targetSignalUrl && this.sendRawMessageToSignalUrl(targetSignalUrl, message)) {
        return;
      }

      this.dependencies.logger.warn('[signaling] Missing peer signal route for outbound raw message', {
        targetPeerId,
        type: messageType
      });
    }

    const serverId = typeof message['serverId'] === 'string' ? message['serverId'] : null;

    if (serverId) {
      const serverSignalUrl = this.dependencies.signalingCoordinator.getServerSignalUrl(serverId);

      if (serverSignalUrl && this.sendRawMessageToSignalUrl(serverSignalUrl, message)) {
        return;
      }

      this.dependencies.logger.warn('[signaling] Missing server signal route for outbound raw message', {
        serverId,
        type: messageType
      });

      return;
    }

    const connectedManagers = this.getConnectedSignalingManagers();

    if (connectedManagers.length === 0) {
      this.dependencies.logger.error('[signaling] No active signaling connection for outbound message', new Error('No signaling manager available'), {
        type: messageType
      });

      return;
    }

    this.dependencies.logger.warn('[signaling] Broadcasting raw message to all signaling managers due to unresolved route', {
      connectedSignalUrls: connectedManagers.map(({ signalUrl }) => signalUrl),
      serverId,
      targetPeerId,
      type: messageType
    });

    for (const { manager } of connectedManagers) {
      manager.sendRawMessage(message);
    }
  }

  sendRawMessageToSignalUrl(signalUrl: string, message: Record<string, unknown>): boolean {
    const manager = this.dependencies.signalingCoordinator.getSignalingManager(signalUrl);

    if (!manager) {
      return false;
    }

    manager.sendRawMessage(message);
    return true;
  }

  identify(
    oderId: string,
    displayName: string,
    signalUrl?: string,
    profile?: Pick<IdentifyCredentials, 'description' | 'profileUpdatedAt'>
  ): void {
    const normalizedDisplayName = displayName.trim() || DEFAULT_DISPLAY_NAME;
    const normalizedDescription = typeof profile?.description === 'string'
      ? (profile.description.trim() || undefined)
      : undefined;
    const normalizedProfileUpdatedAt = typeof profile?.profileUpdatedAt === 'number'
      && Number.isFinite(profile.profileUpdatedAt)
      && profile.profileUpdatedAt > 0
      ? profile.profileUpdatedAt
      : undefined;

    this.lastIdentifyCredentials = {
      oderId,
      displayName: normalizedDisplayName,
      description: normalizedDescription,
      profileUpdatedAt: normalizedProfileUpdatedAt
    };

    if (signalUrl) {
      this.sendRawMessageToSignalUrl(signalUrl, {
        type: SIGNALING_TYPE_IDENTIFY,
        oderId,
        displayName: normalizedDisplayName,
        description: normalizedDescription,
        profileUpdatedAt: normalizedProfileUpdatedAt,
        connectionScope: signalUrl
      });

      return;
    }

    const connectedManagers = this.getConnectedSignalingManagers();

    if (connectedManagers.length === 0) {
      return;
    }

    for (const { signalUrl: managerSignalUrl, manager } of connectedManagers) {
      manager.sendRawMessage({
        type: SIGNALING_TYPE_IDENTIFY,
        oderId,
        displayName: normalizedDisplayName,
        description: normalizedDescription,
        profileUpdatedAt: normalizedProfileUpdatedAt,
        connectionScope: managerSignalUrl
      });
    }
  }
}
