/**
 * Unit test for opt-in backup-bundle encryption
 * (`src/lib/services/backup-destinations/bundle-encryption.ts`).
 *
 * Contract: AES-256-GCM streaming encryption with a scrypt-derived key.
 * - round-trips arbitrary bytes (small + multi-chunk),
 * - rejects the wrong passphrase (GCM auth failure),
 * - rejects tampered ciphertext (GCM auth failure).
 */

import { createHash, randomBytes } from 'node:crypto';
import { mkdtempSync, readFileSync, rmSync, writeFileSync, statSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';

import { afterEach, beforeEach, describe, expect, it } from 'vitest';

import {
  decryptFileToFile,
  encryptFileToFile,
} from '@/lib/services/backup-destinations/bundle-encryption';

function sha(p: string): string {
  return createHash('sha256').update(readFileSync(p)).digest('hex');
}

describe('backup bundle encryption', () => {
  let dir: string;
  beforeEach(() => {
    dir = mkdtempSync(path.join(tmpdir(), 'pn-enc-'));
  });
  afterEach(() => {
    rmSync(dir, { recursive: true, force: true });
  });

  it('round-trips a multi-chunk file with the correct passphrase', async () => {
    const plain = path.join(dir, 'bundle.tar');
    // ~400 KB of pseudo-random bytes → multiple cipher chunks.
    writeFileSync(plain, randomBytes(400 * 1024));
    const enc = path.join(dir, 'bundle.tar.enc');
    const dec = path.join(dir, 'bundle.roundtrip.tar');

    await encryptFileToFile(plain, enc, 'correct horse battery staple');
    // Ciphertext must differ from plaintext and not be empty.
    expect(statSync(enc).size).toBeGreaterThan(0);
    expect(sha(enc)).not.toBe(sha(plain));

    await decryptFileToFile(enc, dec, 'correct horse battery staple');
    expect(sha(dec)).toBe(sha(plain));
  });

  it('rejects the wrong passphrase', async () => {
    const plain = path.join(dir, 'b.tar');
    writeFileSync(plain, Buffer.from('top secret contract bytes'));
    const enc = path.join(dir, 'b.tar.enc');
    await encryptFileToFile(plain, enc, 'right-pass');

    await expect(decryptFileToFile(enc, path.join(dir, 'out.tar'), 'WRONG-pass')).rejects.toThrow();
  });

  it('rejects tampered ciphertext', async () => {
    const plain = path.join(dir, 'c.tar');
    writeFileSync(plain, randomBytes(64 * 1024));
    const enc = path.join(dir, 'c.tar.enc');
    await encryptFileToFile(plain, enc, 'pw');

    // Flip a byte in the middle of the ciphertext region.
    const buf = readFileSync(enc);
    const mid = Math.floor(buf.length / 2);
    buf[mid] = (buf[mid] ?? 0) ^ 0xff;
    writeFileSync(enc, buf);

    await expect(decryptFileToFile(enc, path.join(dir, 'out.tar'), 'pw')).rejects.toThrow();
  });
});
