const semver = require("semver");
const { exec } = require("child_process");
const { readFileSync } = require('fs');
const { pool } = require('./db/connection');
const { info } = require('./db');
const pkg = require("../package.json");
const { ALERT, ERROR, WARNING, NOTICE, INFO, DEBUG } = require('DOUGAL_ROOT/debug')(__filename);


function compatible () {
	return Promise.all([
		compatible_schema()
	]);
}

/** Report whether the database schema version is
 * compatible with the version required by this server.
 *
 * The current schema version is retrieved from the
 * public.info table.
 *
 * The wanted version is retrieved from package.json
 * (config.db_schema).
 *
 * @returns true if the versions are compatible,
 * false otherwise.
 */
function compatible_schema () {
	return new Promise ( async (resolve, reject) => {
		const current = await schema_version();
		const wanted = pkg.config.wanted.db_schema;
		const component = "schema";
		if (semver.satisfies(current, wanted)) {
			resolve({current, wanted, component});
		} else {
			reject({current, wanted, component});
		}
	});
}

async function db_version () {
	const client = await pool.connect();
	let res;
	try {
		res = (await client.query("SELECT version(), PostGIS_Version();"))?.rows[0];
	} catch (err) {
		ERROR(err);
	} finally {
		client.release();
	}
	return res;
}

async function os_version () {
	try {
		return Object.fromEntries(
			readFileSync("/etc/os-release")
			.toString("utf-8")
			.split("\n")
			.map(i =>
				i.split("=", 2)
				.map(i => i.replace(/^"|"$/g, ""))
			)
			.filter(i =>
				["ID", "NAME", "PRETTY_NAME", "VERSION", "VERSION_ID"].includes(i[0])
			)
		);
	} catch (err) {
		ERROR(err);
	}
}

async function schema_version () {
	return await info.get(null, "version/db_schema");
}

/** Return software name.
 *
 */
function app_name () {
	return pkg.name ?? pkg.description ?? "Unknown";
}

/** Return software version, from Git if possible.
 *
 */
async function describe () {
	return new Promise( (resolve, reject) => {
		const cmd = `git describe || echo git+$(git describe --always);`;
		exec(cmd, {cwd: __dirname}, (error, stdout, stderr) => {
			if (error) {
				reject(error);
			}

			if (stdout) {
				resolve(stdout.trim());
			} else {
				// Most likely not installed from Git, use the
				// version number in package.json.
				resolve(pkg.version ?? "Unknown")
			}
		})
	});
}

/** Return version history, from git tags
 *
 */
async function history (count=5, lines=15) {
	return new Promise( (resolve, reject) => {
		const separator2 = String(Math.random()).substring(2)+String(Math.random()).substring(2);
		const separator1 = separator2.repeat(2);
		const cmd = `for tag in $(git tag --sort=-version:refname |head -n ${count}); do printf "\n${separator1}\n"; echo "$tag"; printf "\n${separator2}\n"; git --no-pager tag -n${lines} "$tag"; done`;

		exec(cmd, {cwd: __dirname}, (error, stdout, stderr) => {
			if (error) {
				reject(error);
			}

			if (stdout) {
				const result = stdout
					.split(separator1)           // Separate each tag
					.map(i => i.trim())          // Trim spaces
					.filter(i => i)              // Filter empty lines
					.map(i => i                  // Now for each tag…
						.split(separator2)       // …separate the tag from the description
						.map(i => i.trim())      // trim spaces in both
						.filter( i => i)         // and remove empty lines
					)

				resolve(Object.fromEntries(result));
				// resolve(result.map( ([k, v]) => ({tag: k, notes: v}) ));
			} else {
				// Most likely not installed from Git, use the
				// version number in package.json.
				resolve()
			}
		})
	});
}

function version_old () {
	return pkg.version;
}

async function version () {
	const name = app_name();
	const server = pkg.version;
	const tag = await describe();
	const schema = await schema_version();
	const db = await db_version();
	const os = await os_version();
	const compatibility = [
		await compatible_schema()
	]

	return {
		name,
		server,
		tag,
		...pkg?.config?.versions,
		schema,
		db,
		os,
		compatibility
	}
}
version.compatible = compatible;
version.name = app_name;
version.describe = describe;
version.history = history;

module.exports = version;
