import { of } from 'rxjs';
import {
  beforeEach,
  describe,
  expect,
  it,
  vi
} from 'vitest';
import type { Store } from '@ngrx/store';
import type { RealtimeSessionFacade } from '../../core/realtime';
import type { ServerDirectoryFacade } from '../../domains/server-directory';
import type { Room, User } from '../../shared-kernel';
import { RoomSignalingConnection } from './room-signaling-connection';

interface EndpointFixture {
  id: string;
  isActive: boolean;
  name: string;
  status: 'offline' | 'online';
  url: string;
}

interface SignalSourceFixture {
  sourceId: string;
  sourceName?: string;
  sourceUrl: string;
}

interface SignalSelectorFixture {
  sourceId: string;
  sourceUrl: string;
}

interface FakeRealtimeSessionFacade {
  connectToSignalingServer: ReturnType<typeof vi.fn>;
  hasJoinedServer: ReturnType<typeof vi.fn>;
  identify: ReturnType<typeof vi.fn>;
  isSignalingConnectedTo: ReturnType<typeof vi.fn>;
  joinRoom: ReturnType<typeof vi.fn>;
  peerId: ReturnType<typeof vi.fn>;
  setCurrentServer: ReturnType<typeof vi.fn>;
  switchServer: ReturnType<typeof vi.fn>;
}

interface FakeServerDirectoryFacade {
  awaitInitialServerHealthCheck: ReturnType<typeof vi.fn>;
  buildRoomSignalSelector: ReturnType<typeof vi.fn>;
  ensureEndpointVersionCompatibility: ReturnType<typeof vi.fn>;
  findServerAcrossActiveEndpoints: ReturnType<typeof vi.fn>;
  getFallbackRoomEndpoints: ReturnType<typeof vi.fn>;
  getServer: ReturnType<typeof vi.fn>;
  getWebSocketUrl: ReturnType<typeof vi.fn>;
  normaliseRoomSignalSource: ReturnType<typeof vi.fn>;
  resolveRoomEndpoint: ReturnType<typeof vi.fn>;
}

interface FakeStore {
  dispatch: ReturnType<typeof vi.fn>;
}

describe('RoomSignalingConnection', () => {
  const room: Room = {
    id: 'room-1',
    name: 'Room One',
    description: '',
    hostId: 'user-1',
    createdAt: 1,
    userCount: 1,
    sourceId: 'primary',
    sourceName: 'Primary',
    sourceUrl: 'https://signal.toju.app'
  };
  const user: User = {
    id: 'user-1',
    oderId: 'peer-a',
    username: 'maomao',
    displayName: 'maomao',
    status: 'online',
    createdAt: 1
  };

  let webrtc: FakeRealtimeSessionFacade;
  let serverDirectory: FakeServerDirectoryFacade;
  let store: FakeStore;

  beforeEach(() => {
    const endpoints = new Map<string, EndpointFixture>([
      [
        'primary',
        {
          id: 'primary',
          name: 'Primary',
          url: 'https://signal.toju.app',
          isActive: true,
          status: 'offline'
        }
      ],
      [
        'fallback',
        {
          id: 'fallback',
          name: 'Sweden',
          url: 'https://signal-sweden.toju.app',
          isActive: true,
          status: 'online'
        }
      ]
    ]);

    webrtc = {
      connectToSignalingServer: vi.fn((url: string) => of(url === 'wss://signal-sweden.toju.app')),
      hasJoinedServer: vi.fn(() => false),
      identify: vi.fn(),
      isSignalingConnectedTo: vi.fn(() => false),
      joinRoom: vi.fn(),
      peerId: vi.fn(() => 'peer-a'),
      setCurrentServer: vi.fn(),
      switchServer: vi.fn()
    };

    serverDirectory = {
      awaitInitialServerHealthCheck: vi.fn(() => Promise.resolve()),
      buildRoomSignalSelector: vi.fn((source: SignalSourceFixture) => ({
        sourceId: source.sourceId,
        sourceUrl: source.sourceUrl
      })),
      ensureEndpointVersionCompatibility: vi.fn((selector: SignalSelectorFixture) =>
        Promise.resolve(selector.sourceId === 'fallback')
      ),
      findServerAcrossActiveEndpoints: vi.fn(() => of(null)),
      getFallbackRoomEndpoints: vi.fn(() => [endpoints.get('fallback')]),
      getServer: vi.fn(() => of(null)),
      getWebSocketUrl: vi.fn((selector: SignalSelectorFixture) => selector.sourceUrl.replace(/^http/, 'ws')),
      normaliseRoomSignalSource: vi.fn((source: SignalSourceFixture) => ({
        sourceId: source.sourceId,
        sourceName: source.sourceName,
        sourceUrl: source.sourceUrl
      })),
      resolveRoomEndpoint: vi.fn((source: SignalSourceFixture) => endpoints.get(source.sourceId) ?? null)
    };

    store = {
      dispatch: vi.fn()
    };
  });

  it('tries fallback endpoints when the primary endpoint is offline', async () => {
    const connection = new RoomSignalingConnection(
      webrtc as unknown as RealtimeSessionFacade,
      serverDirectory as unknown as ServerDirectoryFacade,
      store as unknown as Store
    );

    connection.beginRoomNavigation(room.id);
    await connection.connectToRoomSignaling(room, user, user.oderId, [room]);

    expect(serverDirectory.ensureEndpointVersionCompatibility).toHaveBeenCalledTimes(2);
    expect(webrtc.connectToSignalingServer).toHaveBeenCalledWith('wss://signal-sweden.toju.app');
    expect(webrtc.joinRoom).toHaveBeenCalledWith(room.id, user.oderId, 'wss://signal-sweden.toju.app');
  });
});
