import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
import { WebSocketServer } from 'ws';
import { PacificaWebSocket } from '../packages/connectors/pacifica/src/wsClient';
import nacl from 'tweetnacl';
import bs58 from 'bs58';
import { signRequest } from '../packages/connectors/pacifica/src/signing';

describe('PacificaWebSocket', () => {
  let server: WebSocketServer;
  let port: number;

  beforeEach(() => {
    server = new WebSocketServer({ port: 0 });
    const address = server.address();
    if (typeof address === 'string' || !address) {
      throw new Error('Failed to obtain server address');
    }
    port = address.port;
  });

  afterEach(async () => {
    for (const client of server.clients) {
      client.close();
    }
    await new Promise(resolve => server.close(resolve));
    vi.restoreAllMocks();
  });

  it('connects, subscribes, and receives heartbeat responses', async () => {
    const messages: string[] = [];

    const client = new PacificaWebSocket({
      url: `ws://127.0.0.1:${port}`,
      heartbeatIntervalMs: 10
    });

    server.on('connection', socket => {
      socket.on('message', data => {
        messages.push(data.toString());
        socket.send(JSON.stringify({ method: 'pong' }));
      });
    });

    const messagePromise = new Promise<string>(resolve => {
      client.once('message', data => resolve(data.toString()));
    });

    client.subscribe('book.BTC', { depth: 1 });
    client.connect();

    // wait for server to receive login & subscribe
    await new Promise(resolve => setTimeout(resolve, 50));

    expect(messages.length).toBeGreaterThanOrEqual(1);
    const subscribe = JSON.parse(messages[0]!);
    expect(subscribe.method).toBe('subscribe');
    expect(subscribe.params.channel).toBe('book.BTC');

    const serverMessage = await messagePromise;
    expect(serverMessage).toContain('pong');

    client.disconnect();
  });

  it('throws when subscribing to private channel without credentials', () => {
    const client = new PacificaWebSocket({
      url: `ws://127.0.0.1:${port}`
    });

    expect(() => client.subscribe('orders.sub-1')).toThrow(
      'Missing credentials for private Pacifica channel orders.sub-1'
    );
  });

  it('injects auth params for private channel subscriptions', async () => {
    const seed = Uint8Array.from({ length: 32 }, (_, i) => i + 1);
    const { secretKey, publicKey } = nacl.sign.keyPair.fromSeed(seed);
    const secret = Buffer.from(secretKey).toString('base64');
    const apiKey = bs58.encode(publicKey);

    vi.spyOn(Date, 'now').mockReturnValue(1_700_000_000_000);

    const messages: string[] = [];

    const client = new PacificaWebSocket({
      url: `ws://127.0.0.1:${port}`,
      apiKey,
      secret,
      subaccount: 'sub-1',
      heartbeatIntervalMs: 10
    });

    server.on('connection', socket => {
      socket.on('message', data => {
        messages.push(data.toString());
      });
    });

    const basePayload = {
      method: 'subscribe' as const,
      params: { channel: 'orders.sub-1' }
    };

    const { headers, timestamp } = signRequest(
      { apiKey, secret, subaccount: 'sub-1' },
      'SUBSCRIBE',
      '/ws',
      basePayload
    );

    const expectedSignature = headers['X-Pacific-Signature'];

    client.subscribe('orders.sub-1');
    client.connect();

    await new Promise(resolve => setTimeout(resolve, 50));

    expect(messages.length).toBeGreaterThanOrEqual(1);
    const subscribe = JSON.parse(messages[0]!);
    expect(subscribe.method).toBe('subscribe');
    expect(subscribe.params.channel).toBe('orders.sub-1');
    expect(subscribe.params.auth).toEqual({
      key: apiKey,
      timestamp,
      signature: expectedSignature,
      subaccount: 'sub-1'
    });

    client.disconnect();
  });
});
