/**
 * C-06: App-layer AES-256-GCM token encryption
 * Tests the encryptToken function by verifying encrypt → format → decrypt round-trip.
 * Since encryptToken is private to sessionService, we test it via crypto directly
 * using the same algorithm.
 */

import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

// Replicate the same encryptToken logic used in sessionService.ts
function encryptToken(plaintext: string, keyHex: string): string {
	const key = Buffer.from(keyHex.length === 64 ? keyHex : keyHex.padEnd(64, '0'), 'hex');
	const iv = randomBytes(12);
	const cipher = createCipheriv('aes-256-gcm', key, iv);
	const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
	const authTag = cipher.getAuthTag();
	return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted.toString('hex')}`;
}

function decryptToken(ciphertext: string, keyHex: string): string {
	const key = Buffer.from(keyHex.length === 64 ? keyHex : keyHex.padEnd(64, '0'), 'hex');
	const [ivHex, authTagHex, encHex] = ciphertext.split(':');
	const iv = Buffer.from(ivHex!, 'hex');
	const authTag = Buffer.from(authTagHex!, 'hex');
	const encrypted = Buffer.from(encHex!, 'hex');
	const decipher = createDecipheriv('aes-256-gcm', key, iv);
	decipher.setAuthTag(authTag);
	return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
}

const TEST_KEY = 'a'.repeat(64); // 64-hex-char = 32-byte AES-256 key

describe('AES-256-GCM token encryption (C-06)', () => {
	it('produces output in iv:authTag:ciphertext format', () => {
		const result = encryptToken('test-token', TEST_KEY);
		const parts = result.split(':');
		expect(parts).toHaveLength(3);
		// IV is 12 bytes → 24 hex chars
		expect(parts[0]).toHaveLength(24);
		// authTag is 16 bytes → 32 hex chars
		expect(parts[1]).toHaveLength(32);
	});

	it('round-trips: decrypt(encrypt(x)) === x', () => {
		const plaintext = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InVzZXIxIn0.sig';
		const encrypted = encryptToken(plaintext, TEST_KEY);
		const decrypted = decryptToken(encrypted, TEST_KEY);
		expect(decrypted).toBe(plaintext);
	});

	it('produces different ciphertext each call (random IV)', () => {
		const token = 'same-token';
		const enc1 = encryptToken(token, TEST_KEY);
		const enc2 = encryptToken(token, TEST_KEY);
		expect(enc1).not.toBe(enc2);
	});

	it('does not include the plaintext in the ciphertext output', () => {
		const token = 'my-secret-jwt-token';
		const result = encryptToken(token, TEST_KEY);
		expect(result).not.toContain(token);
	});

	it('throws when ciphertext is tampered (GCM auth tag validation)', () => {
		const token = 'valid-token';
		const encrypted = encryptToken(token, TEST_KEY);
		// Flip the last character of the ciphertext part
		const parts = encrypted.split(':');
		const enc = parts[2]!;
		parts[2] = enc.slice(0, -1) + (enc.endsWith('a') ? 'b' : 'a');
		const tampered = parts.join(':');
		expect(() => decryptToken(tampered, TEST_KEY)).toThrow();
	});

	it('does not log or expose plaintext (no SQL involved — pure crypto)', () => {
		// Verifies that encryption is entirely in-process (no mock needed for sequelize)
		const consoleSpy = jest.spyOn(console, 'log');
		encryptToken('sensitive-jwt', TEST_KEY);
		expect(consoleSpy).not.toHaveBeenCalledWith(expect.stringContaining('sensitive-jwt'));
		consoleSpy.mockRestore();
	});
});
