import Logger from '@joplin/utils/Logger';
import shim from '../../../shim';
import { downloadMediaFile, createResourcesFromPaths } from './notes';
import Setting from '../../../models/Setting';
import { readFile, readdir, remove, writeFile } from 'fs-extra';
import Resource from '../../../models/Resource';
import Api, { RequestMethod } from '../Api';
import Note from '../../../models/Note';
import { setupDatabaseAndSynchronizer, switchClient } from '../../../testing/test-utils';
import Revision from '../../../models/Revision';
import { ModelType } from '../../../BaseModel';
const md5 = require('md5');

const imagePath = `${__dirname}/../../../images/SideMenuHeader.png`;
const jpgBase64Content = '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/wAALCAAFAAUBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EAB8QAAEEAQUBAAAAAAAAAAAAAAQBAgUGAwAREiExM//aAAgBAQAAPwBJarVpGHm7KWbapCSwyZ6FDjkLyYE1W/LHyV2zfOk2TrzX/9k=';

describe('routes/notes', () => {

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

	test.each([
		'/invalid/url',
		'htp/asdfasf.com',
		'https//joplinapp.org',
	])('should not return a local file for invalid protocols', async (invalidUrl) => {
		expect(await downloadMediaFile(invalidUrl)).toBe('');
	});

	test.each([
		'https://joplinapp.org/valid/image_url.png',
		'http://joplinapp.org/valid/image_url.png',
	])('should try to download and return a local path to a valid URL', async (url) => {
		const fetchBlobSpy = jest.fn(async (_url, options) => {
			await writeFile(options.path, Buffer.from(jpgBase64Content, 'base64'));
		});
		const spy = jest.spyOn(shim, 'fetchBlob').mockImplementation(fetchBlobSpy);

		const response = await downloadMediaFile(url);

		const files = await readdir(Setting.value('tempDir'));

		expect(files.length).toBe(1);
		expect(fetchBlobSpy).toHaveBeenCalledTimes(1);
		expect(response).toBe(`${Setting.value('tempDir')}/${files[0]}`);
		await remove(response);
		spy.mockRestore();
	});

	test('should get file from local drive if protocol allows it', async () => {
		const url = `file:///${imagePath}`;
		const originalFileContent = await readFile(imagePath);

		const response = await downloadMediaFile(url, null, ['file:']);

		const files = await readdir(Setting.value('tempDir'));
		expect(files.length).toBe(1);
		expect(response).toBe(`${Setting.value('tempDir')}/${files[0]}`);

		const responseFileContent = await readFile(response);
		expect(md5(responseFileContent)).toBe(md5(originalFileContent));
		await remove(response);
	});

	test('should be able to handle URLs with data', async () => {
		const url = 'data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7';
		const originalFileContent = Buffer.from(url.split('data:image/gif;base64,')[1], 'base64');

		const response = await downloadMediaFile(url);

		const files = await readdir(Setting.value('tempDir'));
		expect(files.length).toBe(1);
		expect(response).toBe(`${Setting.value('tempDir')}/${files[0]}`);

		const responseFileContent = await readFile(response);
		expect(md5(responseFileContent)).toBe(md5(originalFileContent));
		await remove(response);
	});

	test('should not process URLs with data that is not image type', async () => {
		const url = 'data:application/octet-stream;base64,dGhpcyBpcyBhIG1lc3NhZ2UK';

		Logger.globalLogger.enabled = false;
		const response = await downloadMediaFile(url);
		Logger.globalLogger.enabled = true;

		const files = await readdir(Setting.value('tempDir'));
		expect(files.length).toBe(0);
		expect(response).toBe('');
	});

	test('should not process URLs from cid: protocol', async () => {
		const url = 'cid:ii_loq3d1100';

		const response = await downloadMediaFile(url);

		const files = await readdir(Setting.value('tempDir'));
		expect(files.length).toBe(0);
		expect(response).toBe('');
	});

	test('should not copy content from invalid protocols', async () => {
		const url = 'file:///home/user/file.db';

		const allowedProtocols: string[] = [];
		const mediaFilePath = await downloadMediaFile(url, null, allowedProtocols);

		const files = await readdir(Setting.value('tempDir'));
		expect(files.length).toBe(0);
		expect(mediaFilePath).toBe('');
	});

	test.each([
		'https://joplinapp.org/valid/image_url',
		'https://joplinapp.org/valid/image_url.invalid_url',
	])('should correct the file extension in filename from files without or invalid ones', async (url) => {
		const spy = jest.spyOn(shim, 'fetchBlob').mockImplementation(async (_url, options) => {
			await writeFile(options.path, Buffer.from(jpgBase64Content, 'base64'));
			return {
				headers: {
					'content-type': 'image/jpg',
				},
			};
		});

		const response = await downloadMediaFile(url);

		const files = await readdir(Setting.value('tempDir'));
		expect(files.length).toBe(1);
		expect(response).toBe(`${Setting.value('tempDir')}/${files[0]}`);

		await remove(response);
		spy.mockRestore();
	});

	test('should be able to create resource from files in the filesystem', async () => {
		const result = await createResourcesFromPaths([
			{ originalUrl: 'asdf.png', path: `${__dirname}/../../../images/SideMenuHeader.png` },
		]);

		const resources = await Resource.all();

		expect(result.length).toBe(1);
		expect(result[0].originalUrl).toBe('asdf.png');
		expect(result[0].path).toBe(`${__dirname}/../../../images/SideMenuHeader.png`);
		expect(result[0].resource.title).toBe('SideMenuHeader.png');
		expect(result[0].resource.file_extension).toBe('png');
		expect(resources.length).toBe(1);
		expect(result[0].resource).toEqual(resources[0]);
	});

	test('should not create resource from files that does not exist', async () => {
		Logger.globalLogger.enabled = false;
		const result = await createResourcesFromPaths([
			{ originalUrl: 'not-a-real-file', path: '/does/not/exist' },
		]);
		Logger.globalLogger.enabled = true;

		expect(result[0].resource).toBe(null);
		const resources = await Resource.all();
		expect(resources.length).toBe(0);
	});

	test('should be able to delete to trash', async () => {
		const api = new Api();
		const note1 = await Note.save({});
		const note2 = await Note.save({});
		const beforeTime = Date.now();
		await api.route(RequestMethod.DELETE, `notes/${note1.id}`);
		await api.route(RequestMethod.DELETE, `notes/${note2.id}`, { permanent: '1' });

		expect((await Note.load(note1.id)).deleted_time).toBeGreaterThanOrEqual(beforeTime);
		expect(await Note.load(note2.id)).toBeFalsy();
	});

	test('should not stop execution if a file can not be processed', async () => {
		Logger.globalLogger.enabled = false;
		const result = await createResourcesFromPaths([
			{ originalUrl: 'asdf.png', path: `${__dirname}/bad-path-should-not-exist` },
			{ originalUrl: 'asdf.png', path: `${__dirname}/../../../images/SideMenuHeader.png` },
		]);
		Logger.globalLogger.enabled = true;

		expect(result.length).toBe(2);
	});

	test('should not return notes in the trash by default', async () => {
		const api = new Api();
		const note1 = await Note.save({});
		const note2 = await Note.save({});
		await Note.delete(note1.id, { toTrash: true });

		{
			const notes = await api.route(RequestMethod.GET, 'notes');
			expect(notes.items.length).toBe(1);
			expect(notes.items[0].id).toBe(note2.id);
		}

		{
			const notes = await api.route(RequestMethod.GET, 'notes', { include_deleted: '1' });
			expect(notes.items.length).toBe(2);
		}
	});

	test('should not return conflicts by default', async () => {
		const api = new Api();
		const note1 = await Note.save({});
		await Note.save({ is_conflict: 1 });

		{
			const notes = await api.route(RequestMethod.GET, 'notes');
			expect(notes.items.length).toBe(1);
			expect(notes.items[0].id).toBe(note1.id);
		}

		{
			const notes = await api.route(RequestMethod.GET, 'notes', { include_conflicts: '1' });
			expect(notes.items.length).toBe(2);
		}
	});

	test('should be able to delete revisions for a note', async () => {
		const api = new Api();
		const note = await Note.save({});
		const revision = await Revision.save({ item_id: note.id, item_type: ModelType.Note, item_updated_time: note.updated_time });
		await api.route(RequestMethod.DELETE, `notes/${note.id}/revisions`);

		expect(await Revision.load(revision.id)).toBeFalsy();
	});

});
