import { describe, test, expect, afterAll } from 'bun:test';
import { SubtleCryptoProvider } from '@shade/crypto-web';
import { sha256Once } from '@shade/streams';
import {
  TransferEngine,
  MemoryControlChannel,
  ShadeTransferHttpTransport,
  ShadeTransferWsTransport,
  FallbackTransferTransport,
  createTransferRoutes,
  type TransferHandle,
  type TransferResult,
} from '../src/index.js';

const crypto = new SubtleCryptoProvider();

function hex(b: Uint8Array): string {
  return Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('');
}

const cleanups: Array<() => void> = [];
afterAll(() => {
  for (const c of cleanups) c();
});

describe('WS opt-in transport with HTTP fallback', () => {
  test('WS connect failure → falls back to HTTP transparently', async () => {
    const { a: ctrlA, b: ctrlB } = MemoryControlChannel.linked('alice', 'bob');
    const receiverEngine = new TransferEngine({
      crypto,
      controlChannel: ctrlB,
      transport: {
        probe: async () => undefined,
        sendChunk: async () => {
          throw new Error('not used');
        },
        fetchResumeState: async () => null,
      },
      myAddress: 'bob',
    });
    const httpApp = await createTransferRoutes(receiverEngine);
    const httpPort = 23000 + Math.floor(Math.random() * 500);
    const httpServer = Bun.serve({ port: httpPort, fetch: httpApp.fetch });
    const httpBaseUrl = `http://localhost:${httpPort}`;
    cleanups.push(() => httpServer.stop());

    const ws = new ShadeTransferWsTransport({
      // Resolve to a closed port — guaranteed connect failure.
      resolveWsUrl: async () => `ws://127.0.0.1:1`,
      connectTimeoutMs: 500,
    });
    const http = new ShadeTransferHttpTransport({
      resolveBaseUrl: async () => httpBaseUrl,
      authenticator: {
        signChunk: async () => ({ 'X-Shade-Sender-Address': 'alice' }),
        signControl: async () => ({ 'X-Shade-Sender-Address': 'alice' }),
      },
    });
    const fallback = new FallbackTransferTransport(ws, http);

    const senderEngine = new TransferEngine({
      crypto,
      controlChannel: ctrlA,
      transport: fallback,
      myAddress: 'alice',
    });
    cleanups.push(() => senderEngine.destroy());
    cleanups.push(() => receiverEngine.destroy());

    let resolveRecv!: (h: TransferHandle) => void;
    const recvHandlePromise = new Promise<TransferHandle>((r) => {
      resolveRecv = r;
    });
    receiverEngine.onIncomingTransfer(async (incoming) => {
      const h = await incoming.accept({ output: { kind: 'buffer' } });
      resolveRecv(h);
    });

    const input = crypto.randomBytes(64 * 1024);
    const handle = await senderEngine.upload({
      to: 'bob',
      input,
      lanes: 2,
      chunkSize: 8 * 1024,
    });
    const recvHandle = await recvHandlePromise;
    const [senderResult, recvResult] = await Promise.all([
      handle.done(),
      recvHandle.done(),
    ]);

    expect(fallback.fellBack).toBe(true);
    const received =
      (recvResult as TransferResult & { bytes?: Uint8Array }).bytes ?? new Uint8Array();
    expect(received).toEqual(input);
    expect(senderResult.sha256).toBe(hex(sha256Once(input)));
  });
});
