import { describe, test, expect } from 'bun:test';
import { createPrekeyServer, MemoryPrekeyStore, PrekeyServerEvents, signPayload } from '../src/index.js';
import type { PrekeyServerEvent } from '../src/index.js';
import { SubtleCryptoProvider } from '@shade/crypto-web';
import { generateIdentityKeyPair } from '@shade/core';

const crypto = new SubtleCryptoProvider();

function b64(bytes: Uint8Array): string {
  return Buffer.from(bytes).toString('base64');
}

function randBytes(n: number): Uint8Array {
  const buf = new Uint8Array(n);
  globalThis.crypto.getRandomValues(buf);
  return buf;
}

describe('PrekeyServerEvents integration', () => {
  test('emits events for register, fetch, replenish, delete', async () => {
    const events = new PrekeyServerEvents();
    const received: PrekeyServerEvent[] = [];
    events.on((e) => received.push(e));

    const store = new MemoryPrekeyStore();
    const app = createPrekeyServer({ crypto, store, disableRateLimit: true, events });

    const alice = await generateIdentityKeyPair(crypto);

    // Register
    const regBody = await signPayload(crypto, alice.signingPrivateKey, {
      address: 'alice',
      identitySigningKey: b64(alice.signingPublicKey),
      identityDHKey: b64(alice.dhPublicKey),
      signedPreKey: { keyId: 1, publicKey: b64(randBytes(32)), signature: b64(randBytes(64)) },
      oneTimePreKeys: [{ keyId: 100, publicKey: b64(randBytes(32)) }],
    });
    await app.request('/v1/keys/register', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(regBody),
    });

    // Fetch bundle
    await app.request('/v1/keys/bundle/alice');

    // Replenish
    const replenishBody = await signPayload(crypto, alice.signingPrivateKey, {
      address: 'alice',
      oneTimePreKeys: [
        { keyId: 200, publicKey: b64(randBytes(32)) },
        { keyId: 201, publicKey: b64(randBytes(32)) },
      ],
    });
    await app.request('/v1/keys/replenish', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(replenishBody),
    });

    // Delete
    const delBody = await signPayload(crypto, alice.signingPrivateKey, { address: 'alice' });
    await app.request('/v1/keys/alice', {
      method: 'DELETE',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(delBody),
    });

    const names = received.map((e) => e.name);
    expect(names).toContain('server.identity_registered');
    expect(names).toContain('server.bundle_fetched');
    expect(names).toContain('server.prekeys_replenished');
    expect(names).toContain('server.identity_deleted');

    // Verify hadOneTimePreKey is true on the fetch event
    const fetchEvent = received.find((e) => e.name === 'server.bundle_fetched');
    expect((fetchEvent!.data as any).hadOneTimePreKey).toBe(true);

    // Verify replenish reports the right count
    const replenishEvent = received.find((e) => e.name === 'server.prekeys_replenished');
    expect((replenishEvent!.data as any).count).toBe(2);
    expect((replenishEvent!.data as any).totalAfter).toBe(2); // 1 - 1 (consumed) + 2 = 2
  });

  test('emits server.rate_limited when limits trip', async () => {
    const events = new PrekeyServerEvents();
    const received: PrekeyServerEvent[] = [];
    events.on((e) => received.push(e));

    // Rate limit ENABLED for this test
    const app = createPrekeyServer({ crypto, store: new MemoryPrekeyStore(), events });

    // Burn the register limit (5/hour)
    for (let i = 0; i < 7; i++) {
      const id = await generateIdentityKeyPair(crypto);
      const body = await signPayload(crypto, id.signingPrivateKey, {
        address: `user${i}`,
        identitySigningKey: b64(id.signingPublicKey),
        identityDHKey: b64(id.dhPublicKey),
        signedPreKey: { keyId: 1, publicKey: b64(randBytes(32)), signature: b64(randBytes(64)) },
      });
      await app.request('/v1/keys/register', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'x-forwarded-for': '203.0.113.99' },
        body: JSON.stringify(body),
      });
    }

    const rateLimitedEvents = received.filter((e) => e.name === 'server.rate_limited');
    expect(rateLimitedEvents.length).toBeGreaterThan(0);
  });

  test('SECURITY: no key material in server event payloads', async () => {
    const events = new PrekeyServerEvents();
    const received: PrekeyServerEvent[] = [];
    events.on((e) => received.push(e));

    const app = createPrekeyServer({ crypto, store: new MemoryPrekeyStore(), disableRateLimit: true, events });
    const alice = await generateIdentityKeyPair(crypto);

    const regBody = await signPayload(crypto, alice.signingPrivateKey, {
      address: 'alice',
      identitySigningKey: b64(alice.signingPublicKey),
      identityDHKey: b64(alice.dhPublicKey),
      signedPreKey: { keyId: 1, publicKey: b64(randBytes(32)), signature: b64(randBytes(64)) },
      oneTimePreKeys: [{ keyId: 100, publicKey: b64(randBytes(32)) }],
    });
    await app.request('/v1/keys/register', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(regBody),
    });
    await app.request('/v1/keys/bundle/alice');

    const json = JSON.stringify(received);
    // Same regex as core: no 32+ byte base64 or 32+ char hex
    const longBase64 = /[A-Za-z0-9+/]{43,}={0,2}/g;
    const longHex = /[0-9a-f]{32,}/gi;
    expect(json.match(longBase64) ?? []).toEqual([]);
    expect(json.match(longHex) ?? []).toEqual([]);
  });
});
