// Krux KEF tests using Bun test runner
import { describe, test, expect } from "bun:test";
import {
  encryptToKrux,
  decryptFromKrux,
  hexToBytes,
  bytesToHex,
  wrap,
  unwrap,
  KruxCipher
} from './krux';
import { getWalletFingerprint } from "./bip32";

describe('Krux KEF Implementation', () => {
  // Test basic hex conversion
  test('hexToBytes and bytesToHex roundtrip', () => {
    const original = 'Hello, World!';
    const bytes = new TextEncoder().encode(original);
    const hex = bytesToHex(bytes);
    const back = hexToBytes(hex);
    expect(new TextDecoder().decode(back)).toBe(original);
  });

  test('hexToBytes handles KEF: prefix', () => {
    const hex = '48656C6C6F';
    const withPrefix = `KEF:${hex}`;
    const bytes1 = hexToBytes(hex);
    const bytes2 = hexToBytes(withPrefix);
    expect(bytes2).toEqual(bytes1);
  });

  test('hexToBytes rejects invalid hex', () => {
    expect(() => hexToBytes('12345')).toThrow('Hex string must have even length');
    expect(() => hexToBytes('12345G')).toThrow('Invalid hex string');
  });

  // Test wrap/unwrap
  test('wrap and unwrap roundtrip', () => {
    const label = 'Test Label';
    const version = 20;
    const iterations = 200000;
    const payload = new TextEncoder().encode('test payload');

    const wrapped = wrap(label, version, iterations, payload);
    const unwrapped = unwrap(wrapped);

    expect(unwrapped.label).toBe(label);
    expect(unwrapped.version).toBe(version);
    expect(unwrapped.iterations).toBe(iterations);
    expect(unwrapped.payload).toEqual(payload);
  });

  test('wrap rejects label too long', () => {
    const longLabel = 'a'.repeat(253); // 253 > 252 max
    const payload = new Uint8Array([1, 2, 3]);

    expect(() => wrap(longLabel, 20, 10000, payload))
      .toThrow('Label too long');
  });

  test('wrap accepts empty label', () => {
    const payload = new Uint8Array([1, 2, 3]);
    const wrapped = wrap('', 20, 10000, payload);
    const unwrapped = unwrap(wrapped);
    expect(unwrapped.label).toBe('');
    expect(unwrapped.version).toBe(20);
    expect(unwrapped.iterations).toBe(10000);
    expect(unwrapped.payload).toEqual(payload);
  });

  test('unwrap rejects invalid envelope', () => {
    expect(() => unwrap(new Uint8Array([1, 2, 3]))).toThrow('Invalid KEF envelope: too short');

    // Label length too large (253 > 252)
    expect(() => unwrap(new Uint8Array([253, 20, 0, 0, 100]))).toThrow('Invalid label length');

    // Empty label (lenId=0) is valid, but need enough data for version+iterations
    // Create a valid envelope with empty label: [0, version, iter1, iter2, iter3, payload...]
    const emptyLabelEnvelope = new Uint8Array([0, 20, 0, 0, 100, 1, 2, 3]);
    const unwrapped = unwrap(emptyLabelEnvelope);
    expect(unwrapped.label).toBe('');
    expect(unwrapped.version).toBe(20);
  });

  // Test encryption/decryption
  test('encryptToKrux and decryptFromKrux roundtrip', async () => {
    const mnemonic = 'test test test test test test test test test test test junk';
    const passphrase = 'secure-passphrase';


    const encrypted = await encryptToKrux({
      mnemonic,
      passphrase,
    });

    const expectedLabel = getWalletFingerprint(mnemonic);

    expect(encrypted.kefBase43).toMatch(/^[0-9A-Z$*+-\./:]+$/); // Check Base43 format
    expect(encrypted.label).toBe(expectedLabel);
    expect(encrypted.iterations).toBe(100000);
    expect(encrypted.version).toBe(20);

    const decrypted = await decryptFromKrux({
      kefData: encrypted.kefBase43, // Use kefBase43 for decryption
      passphrase,
    });

    expect(decrypted.mnemonic).toBe(mnemonic);
    expect(decrypted.label).toBe(expectedLabel);
    expect(decrypted.iterations).toBe(100000);
    expect(decrypted.version).toBe(20);
  });

  test('encryptToKrux requires passphrase', async () => {
    await expect(encryptToKrux({
      mnemonic: 'test',
      passphrase: '',
    })).rejects.toThrow('Passphrase is required');
  });

  test('decryptFromKrux requires passphrase', async () => {
    await expect(decryptFromKrux({
      kefData: '123456',
      passphrase: '',
    })).rejects.toThrow('Invalid Krux data: Not a valid Hex or Base43 string.'); // Updated error message
  });

  test('wrong passphrase fails decryption', async () => {
    const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
    const correctPassphrase = 'correct';
    const wrongPassphrase = 'wrong';

    const { kefBase43 } = await encryptToKrux({ mnemonic, passphrase: correctPassphrase });

    await expect(decryptFromKrux({
      kefData: kefBase43,
      passphrase: wrongPassphrase,
    })).rejects.toThrow('Krux decryption failed - wrong passphrase or corrupted data');
  });

  // Test KruxCipher class directly
  test('KruxCipher encrypt/decrypt roundtrip', async () => {
    const cipher = new KruxCipher('passphrase', new TextEncoder().encode('salt'), 10000);
    const plaintext = new TextEncoder().encode('secret message');

    const encrypted = await cipher.encrypt(plaintext);
    const decrypted = await cipher.decrypt(encrypted, 20);

    expect(new TextDecoder().decode(decrypted)).toBe('secret message');
  });

  test('KruxCipher rejects unsupported version', async () => {
    const cipher = new KruxCipher('passphrase', new TextEncoder().encode('salt'), 10000);
    const plaintext = new Uint8Array([1, 2, 3]);

    await expect(cipher.encrypt(plaintext, 99)).rejects.toThrow('Unsupported KEF version');
    await expect(cipher.decrypt(new Uint8Array(50), 99)).rejects.toThrow('Unsupported KEF version'); // Changed error message
  });

  test('KruxCipher rejects short payload', async () => {
    const cipher = new KruxCipher('passphrase', new TextEncoder().encode('salt'), 10000);
    // Version 20: IV (12) + auth (4) = 16 bytes minimum
    const shortPayload = new Uint8Array(15); // Too short for IV + GCM tag (needs at least 16)

    await expect(cipher.decrypt(shortPayload, 20)).rejects.toThrow('Payload too short for AES-GCM');
  });

  test('iterations scaling works correctly', () => {
    const label = 'Test';
    const version = 20;
    const payload = new TextEncoder().encode('test payload');

    const wrapped1 = wrap(label, version, 200000, payload);
    expect(wrapped1[6]).toBe(0);
    expect(wrapped1[7]).toBe(0);
    expect(wrapped1[8]).toBe(20);

    const wrapped2 = wrap(label, version, 10001, payload);
    const iterStart = 2 + label.length;
    const iters = (wrapped2[iterStart] << 16) | (wrapped2[iterStart + 1] << 8) | wrapped2[iterStart + 2];
    expect(iters).toBe(10001);
  });

  // New test case for user-provided KEF string - this one already uses base43Decode
  test('should correctly decrypt the user-provided KEF string', async () => {
    const kefData = "1334+HGXM$F8PPOIRNHX0.R*:SBMHK$X88LX$*/Y417R/6S1ZQOB2LHM-L+4T1YQVU:B*CKGXONP7:Y/R-B*:$R8FK";
    const passphrase = "aaa";
    const expectedMnemonic = "differ release beauty fresh tortoise usage curtain spoil october town embrace ridge rough reject cabin snap glimpse enter book coach green lonely hundred mercy";

    const result = await decryptFromKrux({ kefData, passphrase });
    expect(result.mnemonic).toBe(expectedMnemonic);
  });
});