import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, encryptionService, expectNotThrow, expectThrow, kvStore, msleep } from '../../testing/test-utils';
import MasterKey from '../../models/MasterKey';
import { activeMasterKeySanityCheck, migrateMasterPassword, migratePpk, resetMasterPassword, showMissingMasterKeyMessage, updateMasterPassword } from './utils';
import { localSyncInfo, masterKeyById, masterKeyEnabled, saveLocalSyncInfo, setActiveMasterKeyId, setMasterKeyEnabled, setPpk } from '../synchronizer/syncInfoUtils';
import Setting from '../../models/Setting';
import { generateKeyPair, generateKeyPairWithAlgorithm, getPpkAlgorithm, ppkPasswordIsValid, testing__setPpkMigrations_ } from './ppk/ppk';
import { PublicKeyAlgorithm } from './types';

describe('e2ee/utils', () => {

	beforeEach(async () => {
		await setupDatabaseAndSynchronizer(1);
		await switchClient(1);
	});

	afterAll(async () => {
		await afterAllCleanUp();
	});

	it('should tell if the missing master key message should be shown', async () => {
		const mk1 = await MasterKey.save(await encryptionService().generateMasterKey('111111'));
		const mk2 = await MasterKey.save(await encryptionService().generateMasterKey('111111'));

		expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id])).toBe(true);
		expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id, mk2.id])).toBe(true);
		expect(showMissingMasterKeyMessage(localSyncInfo(), [])).toBe(false);

		setMasterKeyEnabled(mk1.id, false);
		expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id])).toBe(false);
		expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id, mk2.id])).toBe(true);

		setMasterKeyEnabled(mk2.id, false);
		expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id, mk2.id])).toBe(false);

		setMasterKeyEnabled(mk1.id, true);
		setMasterKeyEnabled(mk2.id, true);
		expect(showMissingMasterKeyMessage(localSyncInfo(), [mk1.id, mk2.id])).toBe(true);

		await expectNotThrow(async () => showMissingMasterKeyMessage(localSyncInfo(), ['not_downloaded_yet']));

		const syncInfo = localSyncInfo();
		syncInfo.masterKeys = [];
		expect(showMissingMasterKeyMessage(syncInfo, [mk1.id, mk2.id])).toBe(false);
	});

	it('should do ppk migration', async () => {
		const { reset } = testing__setPpkMigrations_([PublicKeyAlgorithm.RsaV1, PublicKeyAlgorithm.RsaV2]);

		try {
			const syncInfo = localSyncInfo();
			const testPassword = 'test--TEST';
			Setting.setValue('encryption.masterPassword', testPassword);
			syncInfo.ppk = await generateKeyPairWithAlgorithm(PublicKeyAlgorithm.RsaV1, encryptionService(), testPassword);
			saveLocalSyncInfo(syncInfo);

			expect(getPpkAlgorithm(localSyncInfo().ppk)).toBe(PublicKeyAlgorithm.RsaV1);
			await migratePpk();
			expect(getPpkAlgorithm(localSyncInfo().ppk)).toBe(PublicKeyAlgorithm.RsaV2);
		} finally {
			reset();
		}
	});

	it('should not migrate ppk if the key is up-to-date', async () => {
		const syncInfo = localSyncInfo();
		const testPassword = 'test 🔑';
		Setting.setValue('encryption.masterPassword', testPassword);
		const originalPpk = await generateKeyPair(encryptionService(), testPassword);
		syncInfo.ppk = originalPpk;
		saveLocalSyncInfo(syncInfo);

		const lastChangeTime = localSyncInfo().keyTimestamp('ppk');
		await migratePpk();
		expect(localSyncInfo().keyTimestamp('ppk')).toBe(lastChangeTime);
		expect(localSyncInfo().ppk.createdTime).toBe(originalPpk.createdTime);
	});

	it('should do the master password migration', async () => {
		const mk1 = await MasterKey.save(await encryptionService().generateMasterKey('111111'));
		const mk2 = await MasterKey.save(await encryptionService().generateMasterKey('222222'));

		Setting.setValue('encryption.passwordCache', {
			[mk1.id]: '111111',
			[mk2.id]: '222222',
		});

		await migrateMasterPassword();

		{
			expect(Setting.value('encryption.masterPassword')).toBe('');
			const newCache = Setting.value('encryption.passwordCache');
			expect(newCache[mk1.id]).toBe('111111');
			expect(newCache[mk2.id]).toBe('222222');
		}

		setActiveMasterKeyId(mk1.id);
		await migrateMasterPassword();

		{
			expect(Setting.value('encryption.masterPassword')).toBe('111111');
			const newCache = Setting.value('encryption.passwordCache');
			expect(newCache[mk1.id]).toBe(undefined);
			expect(newCache[mk2.id]).toBe('222222');
		}
	});

	it('should update the master password', async () => {
		const masterPassword1 = '111111';
		const masterPassword2 = '222222';
		Setting.setValue('encryption.masterPassword', masterPassword1);
		const mk1 = await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
		const mk2 = await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));

		setPpk(await generateKeyPair(encryptionService(), masterPassword1));

		await updateMasterPassword(masterPassword1, masterPassword2);

		expect(Setting.value('encryption.masterPassword')).toBe(masterPassword2);
		expect(await ppkPasswordIsValid(encryptionService(), localSyncInfo().ppk, masterPassword1)).toBe(false);
		expect(await ppkPasswordIsValid(encryptionService(), localSyncInfo().ppk, masterPassword2)).toBe(true);
		expect(await encryptionService().checkMasterKeyPassword(await MasterKey.load(mk1.id), masterPassword1)).toBe(false);
		expect(await encryptionService().checkMasterKeyPassword(await MasterKey.load(mk2.id), masterPassword1)).toBe(false);
		expect(await encryptionService().checkMasterKeyPassword(await MasterKey.load(mk1.id), masterPassword2)).toBe(true);
		expect(await encryptionService().checkMasterKeyPassword(await MasterKey.load(mk2.id), masterPassword2)).toBe(true);

		await expectThrow(async () => updateMasterPassword('wrong', masterPassword1));
	});

	it('should set and verify master password when a data key exists', async () => {
		const password = '111111';

		await MasterKey.save(await encryptionService().generateMasterKey(password));

		await expectThrow(async () => updateMasterPassword('', 'wrong'));
		await expectNotThrow(async () => updateMasterPassword('', password));
		expect(Setting.value('encryption.masterPassword')).toBe(password);
	});

	it('should set and verify master password when a private key exists', async () => {
		const password = '111111';

		setPpk(await generateKeyPair(encryptionService(), password));

		await expectThrow(async () => updateMasterPassword('', 'wrong'));
		await expectNotThrow(async () => updateMasterPassword('', password));
		expect(Setting.value('encryption.masterPassword')).toBe(password);
	});

	it('should only set the master password if not already set', async () => {
		expect(localSyncInfo().ppk).toBeFalsy();
		await updateMasterPassword('', '111111');
		expect(Setting.value('encryption.masterPassword')).toBe('111111');
		expect(localSyncInfo().ppk).toBeFalsy();
		expect(localSyncInfo().masterKeys.length).toBe(0);
	});

	it('should change the master password even if no key is present', async () => {
		await updateMasterPassword('', '111111');
		expect(Setting.value('encryption.masterPassword')).toBe('111111');

		await updateMasterPassword('111111', '222222');
		expect(Setting.value('encryption.masterPassword')).toBe('222222');
	});

	it('should reset a master password', async () => {
		const masterPassword1 = '111111';
		const masterPassword2 = '222222';
		Setting.setValue('encryption.masterPassword', masterPassword1);
		const mk1 = await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
		const mk2 = await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
		setPpk(await generateKeyPair(encryptionService(), masterPassword1));

		const previousPpk = localSyncInfo().ppk;
		await resetMasterPassword(encryptionService(), kvStore(), null, masterPassword2);

		expect(masterKeyEnabled(masterKeyById(mk1.id))).toBe(false);
		expect(masterKeyEnabled(masterKeyById(mk2.id))).toBe(false);
		expect(localSyncInfo().ppk.id).not.toBe(previousPpk.id);
		expect(localSyncInfo().ppk.privateKey.ciphertext).not.toBe(previousPpk.privateKey.ciphertext);
		expect(localSyncInfo().ppk.publicKey).not.toBe(previousPpk.publicKey);

		// Also check that a new master key has been created, that it is active and enabled
		expect(localSyncInfo().masterKeys.length).toBe(3);
		expect(localSyncInfo().activeMasterKeyId).toBe(localSyncInfo().masterKeys[2].id);
		expect(masterKeyEnabled(localSyncInfo().masterKeys[2])).toBe(true);
	});

	it('should fix active key selection issues - 1', async () => {
		const masterPassword1 = '111111';
		Setting.setValue('encryption.masterPassword', masterPassword1);
		const mk1 = await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
		await msleep(1);
		await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
		await msleep(1);
		const mk3 = await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
		setActiveMasterKeyId(mk1.id);
		setMasterKeyEnabled(mk1.id, false);

		activeMasterKeySanityCheck();

		const syncInfo = localSyncInfo();
		expect(syncInfo.activeMasterKeyId).toBe(mk3.id);
	});

	it('should fix active key selection issues - 2', async () => {
		// Should not do anything if the active key is already an enabled one.
		const masterPassword1 = '111111';
		Setting.setValue('encryption.masterPassword', masterPassword1);
		const mk1 = await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
		await msleep(1);
		await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
		setActiveMasterKeyId(mk1.id);

		activeMasterKeySanityCheck();

		const syncInfo = localSyncInfo();
		expect(syncInfo.activeMasterKeyId).toBe(mk1.id);
	});

});
