import { describe, test, expect } from 'bun:test';
import {
  toBase64, fromBase64,
  serializeIdentityKeyPair, deserializeIdentityKeyPair,
  serializeSignedPreKey, deserializeSignedPreKey,
  serializeOneTimePreKey, deserializeOneTimePreKey,
  serializeSessionState, deserializeSessionState,
} from '../src/serialization.js';
import type { IdentityKeyPair, SignedPreKey, OneTimePreKey, SessionState } from '../src/types.js';

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

describe('Serialization', () => {
  describe('base64', () => {
    test('roundtrip for various lengths', () => {
      for (const len of [0, 1, 12, 32, 64, 256]) {
        const buf = randBytes(len);
        expect(fromBase64(toBase64(buf))).toEqual(buf);
      }
    });
  });

  describe('IdentityKeyPair', () => {
    test('roundtrip', () => {
      const ikp: IdentityKeyPair = {
        signingPublicKey: randBytes(32),
        signingPrivateKey: randBytes(32),
        dhPublicKey: randBytes(32),
        dhPrivateKey: randBytes(32),
      };
      const json = serializeIdentityKeyPair(ikp);
      const restored = deserializeIdentityKeyPair(json);
      expect(restored.signingPublicKey).toEqual(ikp.signingPublicKey);
      expect(restored.signingPrivateKey).toEqual(ikp.signingPrivateKey);
      expect(restored.dhPublicKey).toEqual(ikp.dhPublicKey);
      expect(restored.dhPrivateKey).toEqual(ikp.dhPrivateKey);
    });
  });

  describe('SignedPreKey', () => {
    test('roundtrip', () => {
      const spk: SignedPreKey = {
        keyId: 42,
        keyPair: { publicKey: randBytes(32), privateKey: randBytes(32) },
        signature: randBytes(64),
        timestamp: Date.now(),
      };
      const restored = deserializeSignedPreKey(serializeSignedPreKey(spk));
      expect(restored.keyId).toBe(42);
      expect(restored.keyPair.publicKey).toEqual(spk.keyPair.publicKey);
      expect(restored.signature).toEqual(spk.signature);
      expect(restored.timestamp).toBe(spk.timestamp);
    });
  });

  describe('OneTimePreKey', () => {
    test('roundtrip', () => {
      const otpk: OneTimePreKey = {
        keyId: 100,
        keyPair: { publicKey: randBytes(32), privateKey: randBytes(32) },
      };
      const restored = deserializeOneTimePreKey(serializeOneTimePreKey(otpk));
      expect(restored.keyId).toBe(100);
      expect(restored.keyPair.publicKey).toEqual(otpk.keyPair.publicKey);
      expect(restored.keyPair.privateKey).toEqual(otpk.keyPair.privateKey);
    });
  });

  describe('SessionState', () => {
    test('roundtrip with all fields', () => {
      const state: SessionState = {
        remoteIdentityKey: randBytes(32),
        rootKey: randBytes(32),
        sendChain: { chainKey: randBytes(32), counter: 5 },
        receiveChain: { chainKey: randBytes(32), counter: 3 },
        dhSend: { publicKey: randBytes(32), privateKey: randBytes(32) },
        dhReceive: randBytes(32),
        previousSendCounter: 4,
        skippedKeys: new Map([
          ['abc:1', randBytes(32)],
          ['def:2', randBytes(32)],
        ]),
      };

      const restored = deserializeSessionState(serializeSessionState(state));
      expect(restored.remoteIdentityKey).toEqual(state.remoteIdentityKey);
      expect(restored.rootKey).toEqual(state.rootKey);
      expect(restored.sendChain.chainKey).toEqual(state.sendChain.chainKey);
      expect(restored.sendChain.counter).toBe(5);
      expect(restored.receiveChain!.chainKey).toEqual(state.receiveChain!.chainKey);
      expect(restored.receiveChain!.counter).toBe(3);
      expect(restored.dhSend.publicKey).toEqual(state.dhSend.publicKey);
      expect(restored.dhReceive).toEqual(state.dhReceive);
      expect(restored.previousSendCounter).toBe(4);
      expect(restored.skippedKeys.size).toBe(2);
      expect(restored.skippedKeys.get('abc:1')).toEqual(state.skippedKeys.get('abc:1'));
      expect(restored.skippedKeys.get('def:2')).toEqual(state.skippedKeys.get('def:2'));
    });

    test('roundtrip with null receiveChain and dhReceive', () => {
      const state: SessionState = {
        remoteIdentityKey: randBytes(32),
        rootKey: randBytes(32),
        sendChain: { chainKey: randBytes(32), counter: 0 },
        receiveChain: null,
        dhSend: { publicKey: randBytes(32), privateKey: randBytes(32) },
        dhReceive: null,
        previousSendCounter: 0,
        skippedKeys: new Map(),
      };

      const restored = deserializeSessionState(serializeSessionState(state));
      expect(restored.receiveChain).toBeNull();
      expect(restored.dhReceive).toBeNull();
      expect(restored.skippedKeys.size).toBe(0);
    });

    test('roundtrip preserves __x3dh metadata', () => {
      const state: SessionState = {
        remoteIdentityKey: randBytes(32),
        rootKey: randBytes(32),
        sendChain: { chainKey: randBytes(32), counter: 0 },
        receiveChain: null,
        dhSend: { publicKey: randBytes(32), privateKey: randBytes(32) },
        dhReceive: null,
        previousSendCounter: 0,
        skippedKeys: new Map(),
      };

      const ephKey = randBytes(32);
      const idKey = randBytes(32);
      (state as any).__x3dh = {
        ephemeralPublicKey: ephKey,
        signedPreKeyId: 1,
        preKeyId: 100,
        identityDHKey: idKey,
        registrationId: 42,
      };

      const restored = deserializeSessionState(serializeSessionState(state));
      const x3dh = (restored as any).__x3dh;
      expect(x3dh).toBeDefined();
      expect(x3dh.ephemeralPublicKey).toEqual(ephKey);
      expect(x3dh.signedPreKeyId).toBe(1);
      expect(x3dh.preKeyId).toBe(100);
      expect(x3dh.identityDHKey).toEqual(idKey);
      expect(x3dh.registrationId).toBe(42);
    });

    test('roundtrip without __x3dh', () => {
      const state: SessionState = {
        remoteIdentityKey: randBytes(32),
        rootKey: randBytes(32),
        sendChain: { chainKey: randBytes(32), counter: 0 },
        receiveChain: null,
        dhSend: { publicKey: randBytes(32), privateKey: randBytes(32) },
        dhReceive: null,
        previousSendCounter: 0,
        skippedKeys: new Map(),
      };

      const restored = deserializeSessionState(serializeSessionState(state));
      expect((restored as any).__x3dh).toBeUndefined();
    });
  });
});
