import {
  Injector,
  runInInjectionContext,
  signal
} from '@angular/core';
import { Store } from '@ngrx/store';
import { Subject } from 'rxjs';
import { RealtimeSessionFacade } from '../../../../core/realtime';
import { selectAllUsers } from '../../../../store/users/users.selectors';
import type { ChatEvent, User } from '../../../../shared-kernel';
import { PeerDeliveryService } from './peer-delivery.service';

describe('PeerDeliveryService', () => {
  it('relays direct messages through signaling when no data channel is connected', () => {
    const context = createServiceContext({ connectedPeers: [], routedPeers: ['bob'] });
    const event: ChatEvent = {
      type: 'direct-message',
      directMessage: {
        message: {
          id: 'message-1',
          conversationId: 'dm-alice-bob',
          senderId: 'alice',
          recipientId: 'bob',
          content: 'hello',
          timestamp: 1,
          status: 'QUEUED'
        },
        sender: {
          userId: 'alice',
          username: 'alice',
          displayName: 'Alice'
        }
      }
    };

    expect(context.service.sendViaWebRTC('bob', event)).toBe(true);
    expect(context.realtime.sendToPeer).not.toHaveBeenCalled();
    expect(context.realtime.sendRawMessage).toHaveBeenCalledWith({
      ...event,
      targetUserId: 'bob'
    });
  });

  it('keeps messages queued when neither P2P nor signaling can reach the recipient', () => {
    const context = createServiceContext({ connectedPeers: [], routedPeers: [] });

    expect(context.service.sendViaWebRTC('bob', { type: 'direct-message' })).toBe(false);
    expect(context.realtime.sendRawMessage).not.toHaveBeenCalled();
  });

  it('emits direct messages received over signaling', () => {
    const context = createServiceContext({ connectedPeers: [] });
    const received: ChatEvent[] = [];

    context.service.directMessageEvents$.subscribe((event) => received.push(event));
    context.signalingMessages.next({ type: 'direct-message' } as ChatEvent);

    expect(received).toEqual([{ type: 'direct-message' }]);
  });
});

interface ServiceContextOptions {
  connectedPeers: string[];
  routedPeers?: string[];
}

interface ServiceContext {
  service: PeerDeliveryService;
  signalingMessages: Subject<ChatEvent>;
  realtime: {
    getConnectedPeers: ReturnType<typeof vi.fn>;
    hasSignalingRouteForPeer: ReturnType<typeof vi.fn>;
    sendRawMessage: ReturnType<typeof vi.fn>;
    sendToPeer: ReturnType<typeof vi.fn>;
  };
}

function createServiceContext(options: ServiceContextOptions): ServiceContext {
  const users = signal<User[]>([createUser('alice', 'Alice'), createUser('bob', 'Bob')]);
  const incomingMessages = new Subject<ChatEvent>();
  const signalingMessages = new Subject<ChatEvent>();
  const peerConnected = new Subject<string>();
  const realtime = {
    onMessageReceived: incomingMessages.asObservable(),
    onSignalingMessage: signalingMessages.asObservable(),
    onPeerConnected: peerConnected.asObservable(),
    getConnectedPeers: vi.fn(() => options.connectedPeers),
    hasSignalingRouteForPeer: vi.fn((peerId: string) => (options.routedPeers ?? []).includes(peerId)),
    sendRawMessage: vi.fn(),
    sendToPeer: vi.fn()
  };
  const store = {
    selectSignal: vi.fn((selector: unknown) => {
      if (selector === selectAllUsers) {
        return users;
      }

      throw new Error('Unexpected selector requested by PeerDeliveryService test.');
    })
  };
  const injector = Injector.create({
    providers: [
      {
        provide: RealtimeSessionFacade,
        useValue: realtime
      },
      {
        provide: Store,
        useValue: store
      }
    ]
  });

  return {
    service: runInInjectionContext(injector, () => new PeerDeliveryService()),
    signalingMessages,
    realtime
  };
}

function createUser(id: string, displayName: string): User {
  return {
    id,
    oderId: id,
    username: displayName.toLowerCase(),
    displayName,
    status: 'online',
    role: 'member',
    joinedAt: 1
  };
}
