import { mockInstance, testDb } from '@n8n/backend-test-utils';
import { DeploymentKeyRepository } from '@n8n/db';
import { Container } from '@n8n/di';
import { Cipher, InstanceSettings } from 'n8n-core';

import { EncryptionBootstrapService } from '../encryption-bootstrap.service';

const INSTANCE_ENCRYPTION_KEY = 'legacy-encryption-key';

beforeAll(async () => {
	mockInstance(InstanceSettings, {
		encryptionKey: INSTANCE_ENCRYPTION_KEY,
		n8nFolder: '/tmp/n8n-test',
		instanceType: 'main',
		canSeedDeploymentState: true,
	});
	await testDb.init();
});

beforeEach(async () => {
	await testDb.truncate(['DeploymentKey']);
});

afterAll(async () => {
	await testDb.terminate();
});

describe('EncryptionBootstrapService (integration)', () => {
	it('creates an inactive CBC key seeded from the instance encryption key', async () => {
		await Container.get(EncryptionBootstrapService).run();

		const rows = await Container.get(DeploymentKeyRepository).find({
			where: { type: 'data_encryption', algorithm: 'aes-256-cbc' },
		});
		expect(rows).toHaveLength(1);
		expect(rows[0].status).toBe('inactive');

		const cipher = Container.get(Cipher);
		const decrypted = cipher.decryptDEKWithInstanceKey(rows[0].value);
		expect(decrypted).toBe(INSTANCE_ENCRYPTION_KEY);
	});

	it('creates an active GCM key', async () => {
		await Container.get(EncryptionBootstrapService).run();

		const rows = await Container.get(DeploymentKeyRepository).find({
			where: { type: 'data_encryption', algorithm: 'aes-256-gcm', status: 'active' },
		});
		expect(rows).toHaveLength(1);
	});

	it('is idempotent — running twice does not create duplicate keys', async () => {
		await Container.get(EncryptionBootstrapService).run();
		await Container.get(EncryptionBootstrapService).run();

		const all = await Container.get(DeploymentKeyRepository).find({
			where: { type: 'data_encryption' },
		});
		const cbcKeys = all.filter((k) => k.algorithm === 'aes-256-cbc');
		const gcmKeys = all.filter((k) => k.algorithm === 'aes-256-gcm' && k.status === 'active');
		expect(cbcKeys).toHaveLength(1);
		expect(gcmKeys).toHaveLength(1);
	});

	it('is race-safe — concurrent bootstraps never create duplicate keys (H7)', async () => {
		const service = Container.get(EncryptionBootstrapService);

		await Promise.all([...Array(5)].map(async () => await service.run()));

		const all = await Container.get(DeploymentKeyRepository).find({
			where: { type: 'data_encryption' },
		});
		const cbcKeys = all.filter((k) => k.algorithm === 'aes-256-cbc');
		const gcmKeys = all.filter((k) => k.algorithm === 'aes-256-gcm' && k.status === 'active');
		expect(cbcKeys).toHaveLength(1);
		expect(gcmKeys).toHaveLength(1);
	});

	it('serializes concurrent seed calls in the repository critical section', async () => {
		// Bypass the service-level fast-path check and hammer the repository
		// directly: the DbLock critical section must let exactly one insert in.
		const repository = Container.get(DeploymentKeyRepository);

		await Promise.all(
			[...Array(5)].map(async (_, i) => await repository.seedLegacyCbcKey(`value-${i}`)),
		);

		const rows = await repository.find({
			where: { type: 'data_encryption', algorithm: 'aes-256-cbc' },
		});
		expect(rows).toHaveLength(1);
	});

	describe('end-to-end write path (real key store, real cipher)', () => {
		beforeEach(() => {
			delete process.env.N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION;
		});

		it('writes the legacy no-prefix format while rotation is off', async () => {
			await Container.get(EncryptionBootstrapService).run();
			const cipher = Container.get(Cipher);

			const encrypted = await cipher.encryptV2('e2e-off');

			expect(encrypted.includes(':')).toBe(false);
			expect(cipher.decryptWithInstanceKey(encrypted)).toBe('e2e-off');
			expect(await cipher.decryptV2(encrypted)).toBe('e2e-off');
		});

		it('writes the keyId-prefixed GCM format while rotation is on, round-tripping through the seeded key', async () => {
			await Container.get(EncryptionBootstrapService).run();
			process.env.N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION = 'true';
			try {
				const cipher = Container.get(Cipher);

				const encrypted = await cipher.encryptV2('e2e-on');

				const active =
					await Container.get(DeploymentKeyRepository).findActiveByType('data_encryption');
				expect(encrypted.startsWith(`${active!.id}:`)).toBe(true);
				expect(await cipher.decryptV2(encrypted)).toBe('e2e-on');
			} finally {
				delete process.env.N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION;
			}
		});
	});
});
