import { describe, test, expect } from 'bun:test';
import {
  encodeEnvelope,
  looksLikeFileEnvelope,
  tryParseEnvelope,
  classify,
  KIND_LIST_V1,
  KIND_ERROR_V1,
  KIND_CANCEL_V1,
  responseKindOf,
} from '../../src/index.js';
import type { RpcRequest, RpcResponse, RpcError, RpcCancel } from '../../src/index.js';

const ID = 'AbCdEfGhIjKlMnOpQrStUv';

describe('looksLikeFileEnvelope', () => {
  test('matches plaintext containing shade.fs', () => {
    expect(looksLikeFileEnvelope('{"kind":"shade.fs.list/v1"}')).toBe(true);
  });
  test('rejects unrelated', () => {
    expect(looksLikeFileEnvelope('hello world')).toBe(false);
    expect(looksLikeFileEnvelope('{"kind":"shade.stream-init/v1"}')).toBe(false);
  });
});

describe('tryParseEnvelope', () => {
  test('returns null on non-JSON', () => {
    expect(tryParseEnvelope('not json {{')).toBeNull();
  });

  test('returns null on JSON that does not match any envelope', () => {
    expect(tryParseEnvelope('{"kind":"unknown"}')).toBeNull();
    expect(tryParseEnvelope('{"foo":"bar"}')).toBeNull();
  });

  test('classifies request', () => {
    const req: RpcRequest = {
      kind: KIND_LIST_V1,
      id: ID,
      args: { path: '/' },
      sig: 'abc',
      signedAt: 1,
    };
    const c = tryParseEnvelope(encodeEnvelope(req));
    expect(c?.kind).toBe('request');
  });

  test('classifies response', () => {
    const resp: RpcResponse = {
      kind: responseKindOf(KIND_LIST_V1),
      id: ID,
      result: { entries: [], hasMore: false },
    };
    const c = tryParseEnvelope(encodeEnvelope(resp));
    expect(c?.kind).toBe('response');
  });

  test('classifies error', () => {
    const err: RpcError = {
      kind: KIND_ERROR_V1,
      id: ID,
      error: { code: 'NOT_FOUND', message: 'missing' },
    };
    const c = tryParseEnvelope(encodeEnvelope(err));
    expect(c?.kind).toBe('error');
  });

  test('classifies cancel', () => {
    const cancel: RpcCancel = {
      kind: KIND_CANCEL_V1,
      id: ID,
      reason: 'user-cancel',
    };
    const c = tryParseEnvelope(encodeEnvelope(cancel));
    expect(c?.kind).toBe('cancel');
  });

  test('rejects request with missing fields', () => {
    expect(tryParseEnvelope(JSON.stringify({ kind: KIND_LIST_V1 }))).toBeNull();
    expect(
      tryParseEnvelope(
        JSON.stringify({ kind: KIND_LIST_V1, id: ID, args: {}, sig: 'x' }),
      ),
    ).toBeNull(); // missing signedAt
  });

  test('rejects response shape with non-.response suffix and no signature', () => {
    // Missing both `.response` suffix (so not a response) AND missing
    // `sig`/`signedAt` (so not a valid request) → no schema matches.
    expect(
      tryParseEnvelope(
        JSON.stringify({ kind: 'shade.fs.list/v1', id: ID, result: {} }),
      ),
    ).toBeNull();
  });

  test('rejects malformed id length', () => {
    const req = {
      kind: KIND_LIST_V1,
      id: 'short',
      args: {},
      sig: 'x',
      signedAt: 1,
    };
    expect(tryParseEnvelope(JSON.stringify(req))).toBeNull();
  });
});

describe('classify on already-validated envelopes', () => {
  test('correct discriminator on each branch', () => {
    const req: RpcRequest = {
      kind: KIND_LIST_V1, id: ID, args: {}, sig: 'x', signedAt: 1,
    };
    expect(classify(req).kind).toBe('request');

    const resp: RpcResponse = {
      kind: responseKindOf(KIND_LIST_V1), id: ID, result: {},
    };
    expect(classify(resp).kind).toBe('response');

    const err: RpcError = {
      kind: KIND_ERROR_V1, id: ID, error: { code: 'NOT_FOUND', message: '' },
    };
    expect(classify(err).kind).toBe('error');

    const cancel: RpcCancel = { kind: KIND_CANCEL_V1, id: ID };
    expect(classify(cancel).kind).toBe('cancel');
  });
});

describe('encodeEnvelope', () => {
  test('roundtrips request envelope', () => {
    const req: RpcRequest = {
      kind: KIND_LIST_V1,
      id: ID,
      args: { path: '/foo' },
      idempotencyKey: 'IdemKeyAaBbCcDdEeFfGgH',
      attempt: 1,
      sig: 'sig',
      signedAt: 1730000000000,
    };
    const c = tryParseEnvelope(encodeEnvelope(req));
    expect(c?.kind).toBe('request');
    if (c?.kind === 'request') {
      expect(c.envelope.idempotencyKey).toBe('IdemKeyAaBbCcDdEeFfGgH');
      expect(c.envelope.attempt).toBe(1);
    }
  });
});
