import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { createShade, type Shade } from '@shade/sdk';
import { createPrekeyServer, MemoryPrekeyStore, PrekeyServerEvents } from '@shade/server';
import { SubtleCryptoProvider } from '@shade/crypto-web';
import type { FileEntry } from '../../src/index.js';

const crypto = new SubtleCryptoProvider();

/**
 * End-to-end test of `Shade.files` — the high-level SDK entrypoint.
 * Verifies that `shade.files.serve(...)` and `shade.files.client(peer)`
 * compose correctly and share a single channel + bridges per Shade.
 */
describe('Shade.files namespace — end-to-end via SDK getter', () => {
  let alice: Shade;
  let bob: Shade;
  let prekeyServer: { stop(): void };
  let aliceServer: { stop(): void };
  let bobServer: { stop(): void };
  let stopBobFiles: (() => Promise<void>) | null = null;

  beforeAll(async () => {
    const prekey = createPrekeyServer({
      crypto,
      store: new MemoryPrekeyStore(),
      disableRateLimit: true,
      events: new PrekeyServerEvents(),
    });
    prekeyServer = Bun.serve({ port: 0, fetch: prekey.fetch });
    const prekeyUrl = `http://localhost:${(prekeyServer as unknown as { port: number }).port}`;

    alice = await createShade({ prekeyServer: prekeyUrl, address: 'alice' });
    bob = await createShade({ prekeyServer: prekeyUrl, address: 'bob' });

    let aliceUrl = '';
    let bobUrl = '';
    alice.configureTransfers({
      resolveBaseUrl: async (peer) => {
        if (peer === 'bob') return bobUrl;
        throw new Error(`unknown peer: ${peer}`);
      },
    });
    bob.configureTransfers({
      resolveBaseUrl: async (peer) => {
        if (peer === 'alice') return aliceUrl;
        throw new Error(`unknown peer: ${peer}`);
      },
    });
    const aliceApp = await alice.transferRoute();
    const bobApp = await bob.transferRoute();
    aliceServer = Bun.serve({ port: 0, fetch: aliceApp.fetch });
    bobServer = Bun.serve({ port: 0, fetch: bobApp.fetch });
    aliceUrl = `http://localhost:${(aliceServer as unknown as { port: number }).port}`;
    bobUrl = `http://localhost:${(bobServer as unknown as { port: number }).port}`;
  });

  afterAll(async () => {
    if (stopBobFiles !== null) await stopBobFiles();
    await alice.files.destroy();
    await bob.files.destroy();
    await alice.shutdown();
    await bob.shutdown();
    aliceServer.stop();
    bobServer.stop();
    prekeyServer.stop();
  });

  test('shade.files getter is memoized', () => {
    const a = bob.files;
    const b = bob.files;
    expect(a).toBe(b);
  });

  test('serve() + client() round-trip stat through the SDK', async () => {
    stopBobFiles = await bob.files.serve({
      stat: async (ctx) => {
        const e: FileEntry = {
          name: ctx.path.split('/').filter(Boolean).pop() ?? '',
          kind: 'file',
          size: 42,
          mtime: 1234,
          metadata: {},
        };
        return e;
      },
    });

    const fs = await alice.files.client('bob');
    const result = await fs.stat('/answer.txt');
    expect(result.name).toBe('answer.txt');
    expect(result.size).toBe(42);
    expect(result.mtime).toBe(1234);
  });

  test('second serve() throws (one handler per Shade)', async () => {
    await expect(
      bob.files.serve({ stat: async () => ({ name: 'x', kind: 'file', size: 0, mtime: 0, metadata: {} }) }),
    ).rejects.toThrow(/handler is already registered/);
  });
});
