import { describe, test, expect } from 'bun:test';
import {
  exportBackup,
  importBackup,
  backupToString,
  backupFromString,
} from '../src/backup.js';
import { SubtleCryptoProvider, MemoryStorage } from '@shade/crypto-web';
import { ShadeSessionManager } from '@shade/core';

const crypto = new SubtleCryptoProvider();

describe('Backup export/import', () => {
  test('roundtrip: export then import reproduces identity', async () => {
    const sourceStorage = new MemoryStorage();
    const manager = new ShadeSessionManager(crypto, sourceStorage);
    await manager.initialize();
    await manager.generateOneTimePreKeys(5);

    const originalId = await sourceStorage.getIdentityKeyPair();
    const originalRegId = await sourceStorage.getLocalRegistrationId();

    // Export
    const blob = await exportBackup(crypto, sourceStorage, 'correct horse battery staple');

    // Import into a fresh storage
    const targetStorage = new MemoryStorage();
    await importBackup(crypto, targetStorage, blob, 'correct horse battery staple');

    const restoredId = await targetStorage.getIdentityKeyPair();
    expect(restoredId).not.toBeNull();
    expect(restoredId!.signingPublicKey).toEqual(originalId!.signingPublicKey);
    expect(restoredId!.dhPrivateKey).toEqual(originalId!.dhPrivateKey);
    expect(await targetStorage.getLocalRegistrationId()).toBe(originalRegId);
    expect(await targetStorage.getOneTimePreKeyCount()).toBe(5);
  });

  test('wrong passphrase fails to import', async () => {
    const storage = new MemoryStorage();
    const manager = new ShadeSessionManager(crypto, storage);
    await manager.initialize();

    const blob = await exportBackup(crypto, storage, 'correct passphrase');

    const target = new MemoryStorage();
    expect(
      importBackup(crypto, target, blob, 'wrong passphrase'),
    ).rejects.toThrow(/Wrong passphrase|corrupted/);
  });

  test('short passphrase is rejected', async () => {
    const storage = new MemoryStorage();
    const manager = new ShadeSessionManager(crypto, storage);
    await manager.initialize();
    expect(exportBackup(crypto, storage, 'short')).rejects.toThrow(/at least 12/);
  });

  test('sessions are included when addresses provided', async () => {
    const aliceStorage = new MemoryStorage();
    const bobStorage = new MemoryStorage();
    const alice = new ShadeSessionManager(crypto, aliceStorage);
    const bob = new ShadeSessionManager(crypto, bobStorage);
    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);
    const env = await alice.encrypt('bob', 'hello');
    await bob.decrypt('alice', env);

    // Export with known addresses
    const blob = await exportBackup(
      crypto,
      aliceStorage,
      'twelve chars minimum',
      ['bob'],
    );

    const target = new MemoryStorage();
    await importBackup(crypto, target, blob, 'twelve chars minimum');

    const restoredSession = await target.getSession('bob');
    expect(restoredSession).not.toBeNull();
  });

  test('backupToString / backupFromString roundtrip', async () => {
    const storage = new MemoryStorage();
    const manager = new ShadeSessionManager(crypto, storage);
    await manager.initialize();

    const blob = await exportBackup(crypto, storage, 'twelve chars minimum');
    const str = backupToString(blob);
    expect(str.startsWith('shade-backup:v1:')).toBe(true);

    const parsed = backupFromString(str);
    expect(parsed.version).toBe(blob.version);
    expect(parsed.salt).toBe(blob.salt);
    expect(parsed.nonce).toBe(blob.nonce);
    expect(parsed.ciphertext).toBe(blob.ciphertext);
  });

  test('invalid backup string format throws', () => {
    expect(() => backupFromString('not-a-valid-backup')).toThrow(/Invalid backup/);
  });
});
