import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { z } from 'zod';
import { setupFileRig, type FileTestRig } from './helpers/rig.js';
import { CustomOpRejectedError, NotImplementedError } from '../../src/index.js';

// Module augmentation: register a typed custom op for the test.
declare module '../../src/index.js' {
  interface CustomOpsMap {
    'test.echo': { args: { message: string }; response: { echoed: string } };
    'test.add': { args: { a: number; b: number }; response: { sum: number } };
  }
}

describe('Custom ops — registry + Zod validation + typed I/O', () => {
  let rig: FileTestRig;
  const callLog: string[] = [];

  beforeAll(async () => {
    rig = await setupFileRig({
      custom: {
        'test.echo': {
          args: z.object({ message: z.string().min(1).max(64) }),
          response: z.object({ echoed: z.string() }),
          handler: async (args, ctx) => {
            callLog.push(`echo:${ctx.sender}:${args.message}`);
            return { echoed: args.message.toUpperCase() };
          },
        },
        'test.add': {
          args: z.object({ a: z.number(), b: z.number() }),
          response: z.object({ sum: z.number() }),
          handler: async (args) => ({ sum: args.a + args.b }),
        },
        'test.bad-response': {
          args: z.object({}),
          response: z.object({ x: z.number() }),
          // Returns wrong shape on purpose.
          handler: async () => ({ y: 'not-a-number' }) as unknown as { x: number },
        },
      },
    });
  });

  afterAll(async () => {
    await rig.teardown();
  });

  test('typed echo round-trips through registered Zod schemas', async () => {
    const result = await rig.fs.custom('test.echo', { message: 'hello' });
    expect(result.echoed).toBe('HELLO');
    expect(callLog).toContain('echo:alice:hello');
  });

  test('typed add', async () => {
    const result = await rig.fs.custom('test.add', { a: 3, b: 4 });
    expect(result.sum).toBe(7);
  });

  test('invalid args (Zod-rejected payload) → InvalidArgsError', async () => {
    await expect(
      // message: '' violates min(1) — TypeScript still allows it since string
      rig.fs.custom('test.echo', { message: '' }),
    ).rejects.toThrow();
  });

  test('unknown custom op name → NotImplementedError', async () => {
    await expect(
      rig.fs.custom('test.unknown' as never, {} as never),
    ).rejects.toBeInstanceOf(NotImplementedError);
  });

  test('handler returns wrong shape → CustomOpRejectedError', async () => {
    await expect(
      rig.fs.custom('test.bad-response' as never, {} as never),
    ).rejects.toBeInstanceOf(CustomOpRejectedError);
  });
});
