import Note from '../../models/Note';
import { encryptionService, msleep, setupDatabaseAndSynchronizer, switchClient } from '../../testing/test-utils';
import ShareService from './ShareService';
import reducer from '../../reducer';
import { createStore } from 'redux';
import { FolderEntity, NoteEntity } from '../database/types';
import Folder from '../../models/Folder';
import { setEncryptionEnabled, setPpk } from '../synchronizer/syncInfoUtils';
import { generateKeyPair } from '../e2ee/ppk';
import MasterKey from '../../models/MasterKey';
import { MasterKeyEntity } from '../e2ee/types';
import { updateMasterPassword } from '../e2ee/utils';

function mockService(api: any) {
	const service = new ShareService();
	const store = createStore(reducer as any);
	service.initialize(store, encryptionService(), api);
	return service;
}

describe('ShareService', function() {

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

	it('should not change the note user timestamps when sharing or unsharing', async () => {
		let note = await Note.save({});
		const service = mockService({
			exec: (method: string, path: string = '', _query: Record<string, any> = null, _body: any = null, _headers: any = null, _options: any = null): Promise<any> => {
				if (method === 'GET' && path === 'api/shares') return { items: [] } as any;
				return null;
			},
			personalizedUserContentBaseUrl(_userId: string) {

			},
		});
		await msleep(1);
		await service.shareNote(note.id);

		function checkTimestamps(previousNote: NoteEntity, newNote: NoteEntity) {
			// After sharing or unsharing, only the updated_time property should
			// be updated, for sync purposes. All other timestamps shouldn't
			// change.
			expect(previousNote.user_created_time).toBe(newNote.user_created_time);
			expect(previousNote.user_updated_time).toBe(newNote.user_updated_time);
			expect(previousNote.updated_time < newNote.updated_time).toBe(true);
			expect(previousNote.created_time).toBe(newNote.created_time);
		}

		{
			const noteReloaded = await Note.load(note.id);
			checkTimestamps(note, noteReloaded);
			note = noteReloaded;
		}

		await msleep(1);
		await service.unshareNote(note.id);

		{
			const noteReloaded = await Note.load(note.id);
			checkTimestamps(note, noteReloaded);
		}
	});

	function testShareFolderService(extraExecHandlers: Record<string, Function> = {}) {
		return mockService({
			exec: async (method: string, path: string, query: Record<string, any>, body: any) => {
				if (extraExecHandlers[`${method} ${path}`]) return extraExecHandlers[`${method} ${path}`](query, body);

				if (method === 'POST' && path === 'api/shares') {
					return {
						id: 'share_1',
					};
				}

				throw new Error(`Unhandled: ${method} ${path}`);
			},
		});
	}

	async function testShareFolder(service: ShareService) {
		const folder = await Folder.save({});
		const note = await Note.save({ parent_id: folder.id });

		const share = await service.shareFolder(folder.id);
		expect(share.id).toBe('share_1');
		expect((await Folder.load(folder.id)).share_id).toBe('share_1');
		expect((await Note.load(note.id)).share_id).toBe('share_1');

		return share;
	}

	it('should share a folder', async () => {
		await testShareFolder(testShareFolderService());
	});

	it('should share a folder - E2EE', async () => {
		setEncryptionEnabled(true);
		await updateMasterPassword('', '111111');
		const ppk = await generateKeyPair(encryptionService(), '111111');
		setPpk(ppk);

		await testShareFolder(testShareFolderService());

		expect((await MasterKey.all()).length).toBe(1);

		const mk = (await MasterKey.all())[0];
		const folder: FolderEntity = (await Folder.all())[0];
		expect(folder.master_key_id).toBe(mk.id);
	});

	it('should add a recipient', async () => {
		setEncryptionEnabled(true);
		await updateMasterPassword('', '111111');
		const ppk = await generateKeyPair(encryptionService(), '111111');
		setPpk(ppk);
		const recipientPpk = await generateKeyPair(encryptionService(), '222222');
		expect(ppk.id).not.toBe(recipientPpk.id);

		let uploadedEmail: string = '';
		let uploadedMasterKey: MasterKeyEntity = null;

		const service = testShareFolderService({
			'POST api/shares': (_query: Record<string, any>, body: any) => {
				return {
					id: 'share_1',
					master_key_id: body.master_key_id,
				};
			},
			'GET api/users/toto%40example.com/public_key': async (_query: Record<string, any>, _body: any) => {
				return recipientPpk;
			},
			'POST api/shares/share_1/users': async (_query: Record<string, any>, body: any) => {
				uploadedEmail = body.email;
				uploadedMasterKey = JSON.parse(body.master_key);
			},
		});

		const share = await testShareFolder(service);

		await service.addShareRecipient(share.id, share.master_key_id, 'toto@example.com');

		expect(uploadedEmail).toBe('toto@example.com');

		const content = JSON.parse(uploadedMasterKey.content);
		expect(content.ppkId).toBe(recipientPpk.id);
	});

});