const { ALERT, ERROR, WARNING, NOTICE, INFO, DEBUG } = require('DOUGAL_ROOT/debug')(__filename);

class ReportLineChangeTime {

	author = `*${this.constructor.name}*`;

	constructor () {
		DEBUG(`${this.author} instantiated`);
	}

	async run (data, ctx) {

		if (!data || data.channel !== "event") {
			return;
		}

		const n = data.payload.new;
		const o = data.payload.old;

		if (!(n?.labels) && !(o?.labels)) {
			return;
		}

		if (!n?.labels?.includes("FGSP") && !o?.labels?.includes("FGSP") &&
			!n?.labels?.includes("LGSP") && !o?.labels?.includes("LGSP")) {
			return;
		}


		try {
			DEBUG("Running");
			const cur = data;
			const projectId = cur?.payload?.pid;
			const forward = (cur?.payload?.old?.labels?.includes("LGSP") || cur?.payload?.new?.labels?.includes("LGSP"));
			DEBUG("%j", cur);

			if (!projectId) {
				throw {message: "No projectID found in event", cur};
				return;
			}

			async function getLineChangeTime (data, forward = false) {
				if (forward) {
					const ospEvents = await ctx.db.event.list(projectId, {label: "FGSP"});
					// DEBUG("ospEvents", ospEvents);
					const osp = ospEvents.filter(i => i.tstamp > data.tstamp).pop();
					DEBUG("fsp", osp);
					// DEBUG("data", data);

					if (osp) {
						DEBUG("lineChangeTime", osp.tstamp - data.tstamp);
						return { lineChangeTime: osp.tstamp - data.tstamp, osp };
					}
				} else {
					const ospEvents = await ctx.db.event.list(projectId, {label: "LGSP"});
					// DEBUG("ospEvents", ospEvents);
					const osp = ospEvents.filter(i => i.tstamp < data.tstamp).shift();
					DEBUG("lsp", osp);
					// DEBUG("data", data);

					if (osp) {
						DEBUG("lineChangeTime", data.tstamp - osp.tstamp);
						return { lineChangeTime: data.tstamp - osp.tstamp, osp };
					}
				}
			}

			function parseInterval (dt) {
				const daySeconds = (dt/1000) % 86400;
				const d = Math.floor((dt/1000) / 86400);
				const dateObject = new Date(null);
				dateObject.setSeconds(daySeconds);
				const [ h, m, s ] = dateObject.toISOString().slice(11, 19).split(":").map(Number);
				return {d, h, m, s};
			}

			function formatInterval (i) {
				let str = "";
				for (let [k, v] of Object.entries(i)) {
					if (v) {
						str += " " + v + " " + k;
					}
				}
				return str.trim();
			}

			const deleteStaleEvents = async (seq) => {
				if (seq) {
					DEBUG("Will delete lct events related to sequence(s)", seq);

					const jpq = `$."${this.author}"`;

					const opts = {jpq};

					if (Array.isArray(seq)) {
						opts.sequences = ctx.unique(seq).filter(i => !!i);
					} else {
						opts.sequence = seq;
					}

					const staleEvents = await ctx.db.event.list(projectId, opts);
					DEBUG(staleEvents.length ?? 0, "events to delete");
					for (let staleEvent of staleEvents) {
						DEBUG(`Deleting event id ${staleEvent.id} (seq = ${staleEvent.sequence}, point = ${staleEvent.point})`);
						if (ctx.dryRun) {
							DEBUG(`await ctx.db.event.del(${projectId}, ${staleEvent.id});`);
						} else {
							await ctx.db.event.del(projectId, staleEvent.id);
						}
					}
				}
			}

			const createLineChangeTimeEvents = async (lineChangeTime, data, osp) => {

				const events = [];
				const cfg = ctx?.projects?.configuration?.[projectId] ?? {};
				const nlcd = cfg?.production?.nominalLineChangeDuration * 60*1000; // m → ms
				DEBUG("nlcd", nlcd);
				if (nlcd && lineChangeTime > nlcd) {
					const excess = lineChangeTime-nlcd;
					const excessString = formatInterval(parseInterval(excess));
					DEBUG("excess", excess, excessString);

					// ref: The later of the two events
					const ref = forward ? osp : data;
					const payload = {
						// tstamp: new Date(ref.tstamp-1),
						sequence: ref.sequence,
						point: ref.point,
						remarks: `_Nominal line change duration exceeded by ${excessString}_`,
						labels: [ "Nav", "Prod" ],
						meta: {
							auto: true,
							author: this.author,
							[this.author]: {
								parents: [
									data.id,
									osp.id
								],
								type: "excess",
								value: excess
							}
						}
					}

					events.push(payload);
					DEBUG("Created line change duration exceeded event", projectId, payload);
				}


				const lctString = formatInterval(parseInterval(lineChangeTime));

				// ref: The later of the two events
				const ref = forward ? osp : data;
				const payload = {
					// tstamp: new Date(ref.tstamp-1),
					sequence: ref.sequence,
					point: ref.point,
					remarks: `Line change time: ${lctString}`,
					labels: [ "Nav", "Prod" ],
					meta: {
						auto: true,
						author: this.author,
						[this.author]: {
							parents: [
								data.id,
								osp.id
							],
							type: "lineChangeTime",
							value: lineChangeTime
						}
					}
				};

				events.push(payload);
				DEBUG("Created line change duration event", projectId, payload);

				return events;
			}

			const maybePostEvent = async (projectId, payload) => {
				DEBUG("Posting event", projectId, payload);
				if (ctx.dryRun) {
					DEBUG(`await ctx.db.event.post(${projectId}, ${payload});`);
				} else {
					await ctx.db.event.post(projectId, payload);
				}
			}


			await deleteStaleEvents([cur.old?.sequence, cur.new?.sequence]);

			if (cur?.payload?.operation == "INSERT") {
				// NOTE: UPDATE on the event_log view translates to one UPDATE plus one INSERT
				// on event_log_full, so we don't need to worry about UPDATE here.
				const data = n;
				DEBUG("INSERT seen: will add lct events related to ", data.id);

				if (ctx.withinValidity(data.validity)) {
					DEBUG("Event within validity period", data.validity, new Date());

					data.tstamp = new Date(data.tstamp);
					const { lineChangeTime, osp } = await getLineChangeTime(data, forward);

					if (lineChangeTime) {

						const events = await createLineChangeTimeEvents(lineChangeTime, data, osp);

						if (events?.length) {
							DEBUG("Deleting other events for sequence", events[0].sequence);
							await deleteStaleEvents(events[0].sequence);
						}

						for (let payload of events) {
							await maybePostEvent(projectId, payload);
						}
					}
				} else {
					DEBUG("Event outside of validity range", data.validity, "lct events not inserted");
				}

			}



		} catch (err) {
			ERROR(`${this.author} error`, err);
			throw err;
		}


	}
}

module.exports = ReportLineChangeTime;
