'use strict';

const fs = require('fs-extra');
const Logger = require('@joplin/lib/Logger').default;
const { dirname } = require('@joplin/lib/path-utils');
const { DatabaseDriverNode } = require('@joplin/lib/database-driver-node.js');
const { JoplinDatabase } = require('@joplin/lib/joplin-database.js');
const BaseModel = require('@joplin/lib/BaseModel').default;
const Folder = require('@joplin/lib/models/Folder.js');
const Note = require('@joplin/lib/models/Note.js');
const Setting = require('@joplin/lib/models/Setting').default;
const { sprintf } = require('sprintf-js');
const exec = require('child_process').exec;

const baseDir = `${dirname(__dirname)}/tests/cli-integration`;
const joplinAppPath = `${__dirname}/main.js`;

const logger = new Logger();
logger.addTarget('console');
logger.setLevel(Logger.LEVEL_ERROR);

const dbLogger = new Logger();
dbLogger.addTarget('console');
dbLogger.setLevel(Logger.LEVEL_INFO);

const db = new JoplinDatabase(new DatabaseDriverNode());
db.setLogger(dbLogger);

function createClient(id) {
	return {
		id: id,
		profileDir: `${baseDir}/client${id}`,
	};
}

const client = createClient(1);

function execCommand(client, command) {
	const exePath = `node ${joplinAppPath}`;
	const cmd = `${exePath} --update-geolocation-disabled --env dev --profile ${client.profileDir} ${command}`;
	logger.info(`${client.id}: ${command}`);

	return new Promise((resolve, reject) => {
		exec(cmd, (error, stdout, stderr) => {
			if (error) {
				logger.error(stderr);
				reject(error);
			} else {
				resolve(stdout.trim());
			}
		});
	});
}

function assertTrue(v) {
	if (!v) throw new Error(sprintf('Expected "true", got "%s"."', v));
	process.stdout.write('.');
}

function assertFalse(v) {
	if (v) throw new Error(sprintf('Expected "false", got "%s"."', v));
	process.stdout.write('.');
}

function assertEquals(expected, real) {
	if (expected !== real) throw new Error(sprintf('Expecting "%s", got "%s"', expected, real));
	process.stdout.write('.');
}

async function clearDatabase() {
	await db.transactionExecBatch(['DELETE FROM folders', 'DELETE FROM notes', 'DELETE FROM tags', 'DELETE FROM note_tags', 'DELETE FROM resources', 'DELETE FROM deleted_items']);
}

const testUnits = {};

testUnits.testFolders = async () => {
	await execCommand(client, 'mkbook nb1');

	let folders = await Folder.all();
	assertEquals(1, folders.length);
	assertEquals('nb1', folders[0].title);

	await execCommand(client, 'mkbook nb1');

	folders = await Folder.all();
	assertEquals(1, folders.length);
	assertEquals('nb1', folders[0].title);

	await execCommand(client, 'rm -r -f nb1');

	folders = await Folder.all();
	assertEquals(0, folders.length);
};

testUnits.testNotes = async () => {
	await execCommand(client, 'mkbook nb1');
	await execCommand(client, 'mknote n1');

	let notes = await Note.all();
	assertEquals(1, notes.length);
	assertEquals('n1', notes[0].title);

	await execCommand(client, 'rm -f n1');
	notes = await Note.all();
	assertEquals(0, notes.length);

	await execCommand(client, 'mknote n1');
	await execCommand(client, 'mknote n2');

	notes = await Note.all();
	assertEquals(2, notes.length);

	await execCommand(client, 'rm -f \'blabla*\'');

	notes = await Note.all();
	assertEquals(2, notes.length);

	await execCommand(client, 'rm -f \'n*\'');

	notes = await Note.all();
	assertEquals(0, notes.length);
};

testUnits.testCat = async () => {
	await execCommand(client, 'mkbook nb1');
	await execCommand(client, 'mknote mynote');

	const folder = await Folder.loadByTitle('nb1');
	const note = await Note.loadFolderNoteByField(folder.id, 'title', 'mynote');

	let r = await execCommand(client, 'cat mynote');
	assertTrue(r.indexOf('mynote') >= 0);
	assertFalse(r.indexOf(note.id) >= 0);

	r = await execCommand(client, 'cat -v mynote');
	assertTrue(r.indexOf(note.id) >= 0);
};

testUnits.testConfig = async () => {
	await execCommand(client, 'config editor vim');
	await Setting.load();
	assertEquals('vim', Setting.value('editor'));

	await execCommand(client, 'config editor subl');
	await Setting.load();
	assertEquals('subl', Setting.value('editor'));

	const r = await execCommand(client, 'config');
	assertTrue(r.indexOf('editor') >= 0);
	assertTrue(r.indexOf('subl') >= 0);
};

testUnits.testCp = async () => {
	await execCommand(client, 'mkbook nb2');
	await execCommand(client, 'mkbook nb1');
	await execCommand(client, 'mknote n1');

	await execCommand(client, 'cp n1');

	const f1 = await Folder.loadByTitle('nb1');
	const f2 = await Folder.loadByTitle('nb2');
	let notes = await Note.previews(f1.id);

	assertEquals(2, notes.length);

	await execCommand(client, 'cp n1 nb2');
	const notesF1 = await Note.previews(f1.id);
	assertEquals(2, notesF1.length);
	notes = await Note.previews(f2.id);
	assertEquals(1, notes.length);
	assertEquals(notesF1[0].title, notes[0].title);
};

testUnits.testLs = async () => {
	await execCommand(client, 'mkbook nb1');
	await execCommand(client, 'mknote note1');
	await execCommand(client, 'mknote note2');
	const r = await execCommand(client, 'ls');

	assertTrue(r.indexOf('note1') >= 0);
	assertTrue(r.indexOf('note2') >= 0);
};

testUnits.testMv = async () => {
	await execCommand(client, 'mkbook nb2');
	await execCommand(client, 'mkbook nb1');
	await execCommand(client, 'mknote n1');
	await execCommand(client, 'mv n1 nb2');

	const f1 = await Folder.loadByTitle('nb1');
	const f2 = await Folder.loadByTitle('nb2');
	let notes1 = await Note.previews(f1.id);
	let notes2 = await Note.previews(f2.id);

	assertEquals(0, notes1.length);
	assertEquals(1, notes2.length);

	await execCommand(client, 'mknote note1');
	await execCommand(client, 'mknote note2');
	await execCommand(client, 'mknote note3');
	await execCommand(client, 'mknote blabla');
	await execCommand(client, 'mv \'note*\' nb2');

	notes1 = await Note.previews(f1.id);
	notes2 = await Note.previews(f2.id);

	assertEquals(1, notes1.length);
	assertEquals(4, notes2.length);
};

async function main() {
	await fs.remove(baseDir);

	logger.info(await execCommand(client, 'version'));

	await db.open({ name: `${client.profileDir}/database.sqlite` });
	BaseModel.setDb(db);
	await Setting.load();

	let onlyThisTest = 'testMv';
	onlyThisTest = '';

	for (const n in testUnits) {
		if (!testUnits.hasOwnProperty(n)) continue;
		if (onlyThisTest && n != onlyThisTest) continue;

		await clearDatabase();
		const testName = n.substr(4).toLowerCase();
		process.stdout.write(`${testName}: `);
		await testUnits[n]();
		console.info('');
	}
}

main(process.argv).catch(error => {
	console.info('');
	logger.error(error);
});