const pathToRegExp = require("path-to-regexp");
const { getCache } = require('./cache');
const { listen } = require('../../../lib/db/notify');
const channels = require('../../../lib/db/channels');
const { ALERT, ERROR, WARNING, NOTICE, INFO, DEBUG } = require('DOUGAL_ROOT/debug')(__filename);

const rels = [
	{
		channels: [ "realtime" ],
		urls: [ ]
	},
	{
		channels: [ "event" ],
		urls: [ "/project/:project/event" ],
	},
	{
		channels: [ "project" ],
		urls: [ "/project/:project" ]
	},
	{
		channels: [ "preplot_lines", "preplot_points" ],
		urls: [ "/project/:project/line" ]
	},
	{
		channels: [ "planned_lines" ],
		urls: [ "/project/:project/plan" ]
	},
	{
		channels: [ "raw_lines", "raw_shots", "final_lines", "final_shots" ],
		urls: [ "/project/:project/sequence" ]
	},
	{
		channels: [ "info" ],
		urls: [ ],
		callback (url, data) {
			if (data.payload?.table == "info") {
				const pid = data.payload?.pid;
				const key = (data.payload?.new ?? data.payload?.old)?.key;

				const rx = /^\/project\/([^\/]+)\/info\/([^\/?]+)[\/?]?/;
				const match = url.match(rx);
				if (match) {
					if (match[1] == pid) {
						if (match[2] == data.payload?.old?.key || match[2] == data.payload?.new?.key) {
							return true;
						}
					}
				}

				if (key == "plan") {
					const rx = /^\/project\/([^\/]+)\/plan[\/?]?/;
					const match = url.match(rx);
					if (match) {
						return match[1] == pid;
					}
				}
			}
			return false;
		}
	},
	{
		channels: [ "queue_items" ],
		urls: [ ]
	},
]

function invalidateCache (data, cache) {
	return new Promise((resolve, reject) => {
		if (!data) {
			ERROR("invalidateCache called with no data");
			return;
		}

		if (!data.payload) {
			ERROR("invalidateCache called without a payload; channel = %s", data.channel);
			return;
		}

		const channel = data.channel;
		const project = data.payload?.pid ?? data.payload?.new?.pid ?? data.payload?.old?.pid;
		const operation = data.payload.operation;
		const table = data.payload.table;
		const fields = { channel, project, operation, table };

// 		DEBUG("Event received: %j", fields);

		for (let rel of rels) {
// 			DEBUG("Checking if channel matches: %j", rel.channels);
			if (rel.channels.includes(channel)) {
				for (let url of rel.urls) {
// 					DEBUG("Checking if url matches: %s", url);

					const matches = typeof url === "function"
						? url
						: pathToRegExp.match(url, {decode: decodeURIComponent, end: false});

					for (let [key, data] of Object.entries(cache)) {
// 						DEBUG("Checking cache entry %s against fields %j", key, fields);

						const { params } = matches(key);
// 						DEBUG("Check result: %j", params);

						if (params && Object.entries(params).every(i => fields[i[0]] == i[1])) {
							DEBUG("Delete entry %s due to match with parameters %j", key, params);
							delete cache[key];
						}

					}
				}

				if (rel.callback) {
					for (let key of Object.keys(cache)) {
						if (rel.callback(key, data)) {
							DEBUG("Delete entry %s due to callback result on channel %s", key, channel);
							delete cache[key];
						}
					}
				}

			}
		}

		resolve();
	});
}

async function watch (app) {
	if (!app.locals?.etags) {
		app.locals.etags = {};
	}

	function etagWatch (data) {
		invalidateCache(data, app.locals.etags);
	}
	try {
		const client = await listen(channels, etagWatch);
		INFO("ETag watch installed");
	} catch (err) {
		ERROR("ETag watch install failed");
		ERROR(err);
	}
}

module.exports = watch;
