import { describe, test, expect } from 'bun:test';
import { SubtleCryptoProvider, MemoryStorage } from '@shade/crypto-web';
import { ShadeSessionManager, GRACE_PERIOD_MS } from '../src/index.js';

const crypto = new SubtleCryptoProvider();

describe('Identity rotation', () => {
  test('rotateIdentity generates new identity and archives old', async () => {
    const storage = new MemoryStorage();
    const mgr = new ShadeSessionManager(crypto, storage);
    await mgr.initialize();

    const oldFp = await mgr.getIdentityFingerprint();
    const oldPub = mgr.getPublicIdentity();

    await mgr.rotateIdentity();

    const newFp = await mgr.getIdentityFingerprint();
    const newPub = mgr.getPublicIdentity();

    // New identity should differ
    expect(newFp).not.toBe(oldFp);
    expect(newPub.signingKey).not.toEqual(oldPub.signingKey);
    expect(newPub.dhKey).not.toEqual(oldPub.dhKey);

    // Old identity is archived
    const retired = await storage.getRetiredIdentities();
    expect(retired.length).toBe(1);
    expect(retired[0].keyPair.signingPublicKey).toEqual(oldPub.signingKey);
  });

  test('rotation returns a new prekey bundle', async () => {
    const mgr = new ShadeSessionManager(crypto, new MemoryStorage());
    await mgr.initialize();

    const bundle = await mgr.rotateIdentity();
    expect(bundle.identitySigningKey.length).toBe(32);
    expect(bundle.identityDHKey.length).toBe(32);
    expect(bundle.signedPreKey.keyId).toBe(2); // advanced
    expect(bundle.signedPreKey.signature.length).toBe(64);
  });

  test('getActiveRetiredIdentities returns entries within grace period', async () => {
    const mgr = new ShadeSessionManager(crypto, new MemoryStorage());
    await mgr.initialize();

    await mgr.rotateIdentity();
    await mgr.rotateIdentity();

    const active = await mgr.getActiveRetiredIdentities();
    expect(active.length).toBe(2);
  });

  test('getActiveRetiredIdentities filters out expired entries', async () => {
    const storage = new MemoryStorage();
    const mgr = new ShadeSessionManager(crypto, storage);
    await mgr.initialize();

    // Manually add a very old retired identity
    await storage.addRetiredIdentity({
      keyPair: (await mgr.getPublicIdentity()) as any, // placeholder
      retiredAt: Date.now() - (GRACE_PERIOD_MS + 1000),
    });

    // And a fresh one
    await mgr.rotateIdentity();

    const active = await mgr.getActiveRetiredIdentities();
    expect(active.length).toBe(1);
  });

  test('pruneExpiredIdentities removes old entries', async () => {
    const storage = new MemoryStorage();
    const mgr = new ShadeSessionManager(crypto, storage);
    await mgr.initialize();

    // Rotate twice
    await mgr.rotateIdentity();
    await mgr.rotateIdentity();

    expect((await storage.getRetiredIdentities()).length).toBe(2);

    // Default grace period: nothing is expired yet
    await mgr.pruneExpiredIdentities();
    expect((await storage.getRetiredIdentities()).length).toBe(2);

    // Force prune with 0 grace → everything goes
    await mgr.pruneExpiredIdentities(0);
    expect((await storage.getRetiredIdentities()).length).toBe(0);
  });

  test('rotation persists across manager restart', async () => {
    const storage = new MemoryStorage();
    const mgr1 = new ShadeSessionManager(crypto, storage);
    await mgr1.initialize();
    await mgr1.rotateIdentity();
    const fp1 = await mgr1.getIdentityFingerprint();

    const mgr2 = new ShadeSessionManager(crypto, storage);
    await mgr2.initialize();
    const fp2 = await mgr2.getIdentityFingerprint();

    expect(fp1).toBe(fp2);
  });

  test('existing sessions survive identity rotation', async () => {
    // Set up Alice-Bob conversation
    const alice = new ShadeSessionManager(crypto, new MemoryStorage());
    const bob = new ShadeSessionManager(crypto, new MemoryStorage());
    await alice.initialize();
    await bob.initialize();

    const otpks = await bob.generateOneTimePreKeys(5);
    const bundle = await bob.createPreKeyBundle();
    bundle.oneTimePreKey = { keyId: otpks[0].keyId, publicKey: otpks[0].keyPair.publicKey };
    await alice.initSessionFromBundle('bob', bundle);

    // Exchange messages to establish bidirectional session
    const env1 = await alice.encrypt('bob', 'hello');
    expect(await bob.decrypt('alice', env1)).toBe('hello');
    const env2 = await bob.encrypt('alice', 'hi');
    expect(await alice.decrypt('bob', env2)).toBe('hi');

    // Alice rotates her identity — but her session with Bob should still work
    // because the Double Ratchet uses ephemeral DH keys, not identity keys
    await alice.rotateIdentity();

    const env3 = await alice.encrypt('bob', 'after rotation');
    expect(await bob.decrypt('alice', env3)).toBe('after rotation');
  });
});
