import { Subject } from 'rxjs';
import {
  DATA_CHANNEL_STATE_OPEN,
  P2P_TYPE_STATE_REQUEST,
  P2P_TYPE_VOICE_STATE_REQUEST
} from '../../realtime.constants';
import { setupDataChannel } from './data-channel';
import {
  createPeerConnectionManagerState,
  DataChannelLifecycleHandlers,
  PeerConnectionManagerContext
} from '../shared';

describe('data channel lifecycle', () => {
  it('sends current state and requests peer state when the channel opens', () => {
    const context = createContext();
    const handlers = createHandlers();
    const channel = createDataChannel(DATA_CHANNEL_STATE_OPEN);

    setupDataChannel(context, channel, 'peer-a', handlers);
    channel.onopen?.(new Event('open'));

    expect(handlers.clearDataChannelRecoveryTimer).toHaveBeenCalledWith('peer-a');
    expect(channel.send).toHaveBeenCalledWith(expect.stringContaining('"type":"voice-state"'));
    expect(channel.send).toHaveBeenCalledWith(JSON.stringify({ type: P2P_TYPE_STATE_REQUEST }));
    expect(handlers.scheduleDataChannelRecovery).not.toHaveBeenCalled();
  });

  it('requests a voice-state resync on a non-fatal data channel error', () => {
    const context = createContext();
    const handlers = createHandlers();
    const channel = createDataChannel(DATA_CHANNEL_STATE_OPEN);

    setupDataChannel(context, channel, 'peer-a', handlers);
    channel.onerror?.(new Event('error'));

    expect(channel.send).toHaveBeenCalledWith(JSON.stringify({ type: P2P_TYPE_VOICE_STATE_REQUEST }));
    expect(handlers.scheduleDataChannelRecovery).not.toHaveBeenCalled();
  });

  it('schedules peer recovery when an error leaves the channel closed', () => {
    const context = createContext();
    const handlers = createHandlers();
    const channel = createDataChannel('closed');

    setupDataChannel(context, channel, 'peer-a', handlers);
    channel.onerror?.(new Event('error'));

    expect(channel.send).not.toHaveBeenCalled();
    expect(handlers.scheduleDataChannelRecovery).toHaveBeenCalledWith('peer-a', channel, 'error');
  });

  it('schedules peer recovery when the data channel closes', () => {
    const context = createContext();
    const handlers = createHandlers();
    const channel = createDataChannel('closed');

    setupDataChannel(context, channel, 'peer-a', handlers);
    channel.onclose?.(new Event('close'));

    expect(handlers.scheduleDataChannelRecovery).toHaveBeenCalledWith('peer-a', channel, 'close');
  });
});

function createContext(): PeerConnectionManagerContext {
  return {
    logger: {
      error: vi.fn(),
      info: vi.fn(),
      logStream: vi.fn(),
      traffic: vi.fn(),
      warn: vi.fn()
    } as unknown as PeerConnectionManagerContext['logger'],
    callbacks: {
      getIceServers: vi.fn(() => []),
      getIdentifyCredentials: vi.fn(() => ({ oderId: 'local-user', token: 'session-token', displayName: 'Local User' })),
      getLocalMediaStream: vi.fn(() => null),
      getLocalPeerId: vi.fn(() => 'local-peer'),
      getVoiceStateSnapshot: vi.fn(() => ({
        isConnected: true,
        isMuted: false,
        isDeafened: false,
        isScreenSharing: false,
        roomId: 'voice-room',
        serverId: 'server-1'
      })),
      isCameraEnabled: vi.fn(() => false),
      isScreenSharingActive: vi.fn(() => false),
      isSignalingConnected: vi.fn(() => true),
      sendRawMessage: vi.fn()
    },
    state: {
      ...createPeerConnectionManagerState(),
      peerConnected$: new Subject<string>()
    }
  };
}

function createHandlers(): DataChannelLifecycleHandlers {
  return {
    clearDataChannelRecoveryTimer: vi.fn(),
    scheduleDataChannelRecovery: vi.fn()
  };
}

function createDataChannel(readyState: RTCDataChannelState): RTCDataChannel & { send: ReturnType<typeof vi.fn> } {
  return {
    addEventListener: vi.fn(),
    binaryType: 'arraybuffer',
    bufferedAmount: 0,
    bufferedAmountLowThreshold: 0,
    close: vi.fn(),
    dispatchEvent: vi.fn(),
    id: 1,
    label: 'chat',
    maxPacketLifeTime: null,
    maxRetransmits: null,
    negotiated: false,
    onbufferedamountlow: null,
    onclose: null,
    onclosing: null,
    onerror: null,
    onmessage: null,
    onopen: null,
    ordered: true,
    protocol: '',
    readyState,
    removeEventListener: vi.fn(),
    send: vi.fn()
  } as unknown as RTCDataChannel & { send: ReturnType<typeof vi.fn> };
}
