import { strict as assert } from 'node:assert';
import { createHash } from 'node:crypto';
import { describe, it } from 'node:test';
import { schnorr } from '@noble/curves/secp256k1.js';
import { getPublicKey, nip19, verifyEvent, type Event } from 'nostr-tools';
import { buzzWorkspaceId } from '../src/routes/buzz';
import {
  BuzzNotifier,
  decodeBuzzAuthorization,
  normaliseBuzzRelayUrl,
  parseAndVerifyBuzzAuthTag,
} from '../src/services/buzz';
import { NotificationDeliveryError } from '../src/services/notifier';
import { loadConfig } from '../src/lib/config';

const AGENT_KEY = `${'0'.repeat(63)}1`;
const CHANNEL = '10000000-0000-4000-8000-000000000001';
const envelope = {
  idempotencyKey: 'buzz:link:stage-event',
  destination: CHANNEL,
  workspaceId: 'buzz.example.com',
  notification: {
    kind: 'stage_change' as const,
    accountId: '20000000-0000-4000-8000-000000000002',
    dealId: '30000000-0000-4000-8000-000000000003',
    dealSide: 'demand' as const,
    dealName: 'Reserved H100 cluster',
    fromStage: 'proposal',
    toStage: 'procurement',
    changedAt: '2026-08-13T12:00:00.000Z',
  },
};

describe('Buzz relay identity', () => {
  it('normalises the WebSocket URL used by Buzz clients into its HTTP bridge community', () => {
    assert.equal(normaliseBuzzRelayUrl('wss://buzz.example.com/'), 'https://buzz.example.com');
    assert.equal(buzzWorkspaceId('wss://buzz.example.com/'), 'buzz.example.com');
  });

  it('accepts official hex and nsec representations as the same identity', () => {
    const nsec = nip19.nsecEncode(Uint8Array.from(Buffer.from(AGENT_KEY, 'hex')));
    const fromHex = new BuzzNotifier({ relayUrl: 'https://buzz.example.com', privateKey: AGENT_KEY });
    const fromNsec = new BuzzNotifier({ relayUrl: 'https://buzz.example.com', privateKey: nsec });
    assert.equal(fromHex.workspaceId, fromNsec.workspaceId);
  });
});

describe('Buzz configuration', () => {
  const base = { DATABASE_URL: 'postgres://pig:pig@localhost:5432/pig', NODE_ENV: 'test' };

  it('requires relay and private identity together', () => {
    assert.throws(
      () => loadConfig({ ...base, BUZZ_RELAY_URL: 'https://buzz.example.com' }),
      /BUZZ_PRIVATE_KEY/,
    );
    assert.throws(
      () => loadConfig({ ...base, BUZZ_PRIVATE_KEY: AGENT_KEY }),
      /BUZZ_RELAY_URL/,
    );
  });

  it('keeps the Buzz private identity in server configuration only', () => {
    const config = loadConfig({
      ...base,
      BUZZ_RELAY_URL: 'https://buzz.example.com',
      BUZZ_PRIVATE_KEY: AGENT_KEY,
    });
    assert.equal(config.BUZZ_RELAY_URL, 'https://buzz.example.com');
    assert.equal(config.BUZZ_PRIVATE_KEY, AGENT_KEY);
  });
});

describe('Buzz signed delivery', () => {
  it('reuses the signed message event id while refreshing NIP-98 replay nonces', async () => {
    const requests: { event: Event; auth: Event }[] = [];
    let nonce = 0;
    const notifier = new BuzzNotifier({
      relayUrl: 'https://buzz.example.com',
      privateKey: AGENT_KEY,
      now: () => new Date('2026-08-13T12:00:01.000Z'),
      nonce: () => `nonce-${++nonce}`,
      fetchImpl: async (_input, init) => {
        const event = JSON.parse(String(init?.body)) as Event;
        const headers = new Headers(init?.headers);
        const auth = decodeBuzzAuthorization(headers.get('authorization') ?? '');
        assert.ok(auth);
        requests.push({ event, auth });
        return Response.json({ event_id: event.id, accepted: true, message: '' });
      },
    });

    await notifier.send(envelope);
    await notifier.send(envelope);
    await notifier.send({ ...envelope, idempotencyKey: 'buzz:link:different-event' });

    assert.equal(requests[0]?.event.id, requests[1]?.event.id);
    assert.notEqual(requests[0]?.event.id, requests[2]?.event.id);
    assert.notEqual(requests[0]?.auth.id, requests[1]?.auth.id);
    assert.equal(requests[0]?.event.kind, 9);
    assert.ok(verifyEvent(requests[0]!.event));
    assert.deepEqual(requests[0]?.event.tags[0], ['h', CHANNEL]);
    const payloadTag = requests[0]?.auth.tags.find((tag) => tag[0] === 'payload');
    assert.equal(
      payloadTag?.[1],
      createHash('sha256').update(JSON.stringify(requests[0]!.event)).digest('hex'),
    );
  });

  it('refuses to route a link from another relay community', async () => {
    let called = false;
    const notifier = new BuzzNotifier({
      relayUrl: 'https://buzz.example.com',
      privateKey: AGENT_KEY,
      fetchImpl: async () => {
        called = true;
        return Response.json({});
      },
    });
    await assert.rejects(
      notifier.send({ ...envelope, workspaceId: 'other.example.com' }),
      (error: unknown) =>
        error instanceof NotificationDeliveryError &&
        error.code === 'buzz_workspace_mismatch' &&
        !error.retryable,
    );
    assert.equal(called, false);
  });

  it('marks relay throttling retryable without exposing response content', async () => {
    const notifier = new BuzzNotifier({
      relayUrl: 'https://buzz.example.com',
      privateKey: AGENT_KEY,
      fetchImpl: async () =>
        new Response('{"error":"rate-limited: private detail"}', {
          status: 429,
          headers: { 'retry-after': '9' },
        }),
    });
    await assert.rejects(
      notifier.send(envelope),
      (error: unknown) =>
        error instanceof NotificationDeliveryError &&
        error.code === 'buzz_rate_limited' &&
        error.retryable &&
        error.retryAfterMs === 9_000 &&
        !error.message.includes('private detail'),
    );
  });
});

describe('Buzz owner attestation', () => {
  it('verifies NIP-OA against the configured agent before sending it', () => {
    const ownerKey = Uint8Array.from(Buffer.from(`${'0'.repeat(63)}2`, 'hex'));
    const agentPublicKey = getPublicKey(Uint8Array.from(Buffer.from(AGENT_KEY, 'hex')));
    const conditions = 'kind=9';
    const digest = createHash('sha256')
      .update(`nostr:agent-auth:${agentPublicKey}:${conditions}`)
      .digest();
    const tag = JSON.stringify([
      'auth',
      getPublicKey(ownerKey),
      conditions,
      Buffer.from(schnorr.sign(digest, ownerKey)).toString('hex'),
    ]);
    assert.deepEqual(parseAndVerifyBuzzAuthTag(tag, agentPublicKey), JSON.parse(tag));
    const tampered = JSON.parse(tag) as string[];
    tampered[3] = `${tampered[3]![0] === '0' ? '1' : '0'}${tampered[3]!.slice(1)}`;
    assert.throws(
      () => parseAndVerifyBuzzAuthTag(JSON.stringify(tampered), agentPublicKey),
      /BUZZ_AUTH_TAG/,
    );
  });
});
