/*
 * @Author: 孙浩林 sunhaolin@steedos.com
 * @Date: 2025-10-09 15:07:29
 * @LastEditors: 孙浩林 sunhaolin@steedos.com
 * @LastEditTime: 2025-10-10 11:43:04
 * @FilePath: /steedos-plugins/steedos-packages/plugin-workflow/src/webhook_queue.js
 * @Description: 
 */
const _ = require("lodash");
const axios = require('axios');

const { getCollection } = require("../main/default/utils/collection");

const WebhookQueue = {};

WebhookQueue.send = async function (options) {
	var currentUser = '<SERVER>'
	var webhook = _.extend({
		createdAt: new Date(),
		createdBy: currentUser
	});

	webhook.webhook = _.pick(options, ['instance', 'current_approve', 'payload_url', 'content_type', 'action', 'from_user', 'to_users']);

	webhook.sent = false;
	webhook.sending = 0;

	return await WebhookQueue.collection.insert(webhook);
};

var isConfigured = false;
var sendWorker = async function (task, interval) {

	if (WebhookQueue.debug) {
		console.log('WebhookQueue: Send worker started, using interval: ' + interval);
	}

	return setInterval(async function () {
		try {
			await task();
		} catch (error) {
			if (WebhookQueue.debug) {
				console.log('WebhookQueue: Error while sending: ' + error.message);
			}
		}
	}, interval);
};

/*
	options: {
		// Controls the sending interval
		sendInterval: Match.Optional(Number),
		// Controls the sending batch size per interval
		sendBatchSize: Match.Optional(Number),
		// Allow optional keeping notifications in collection
		keepWebhooks: Match.Optional(Boolean)
	}
*/
WebhookQueue.Configure = async function (options) {
	WebhookQueue.collection = await getCollection('_webhook_queue');

	var self = this;
	options = _.extend({
		sendTimeout: 60000, // Timeout period for webhook send
	}, options);

	// Block multiple calls
	if (isConfigured) {
		throw new Error('WebhookQueue.Configure should not be called more than once!');
	}

	isConfigured = true;

	// Add debug info
	if (WebhookQueue.debug) {
		console.log('WebhookQueue.Configure', options);
	}

	self.sendWebhook = async function (webhook) {
		if (WebhookQueue.debug) {
			console.log("sendWebhook");
			console.log(webhook);
		}

		try {
			const response = await axios({
				method: 'post',
				url: webhook.webhook.payload_url,
				headers: {
					"Content-Type": webhook.webhook.content_type
				},
				data: {
					instance: webhook.webhook.instance,
					current_approve: webhook.webhook.current_approve,
					action: webhook.webhook.action,
					from_user: webhook.webhook.from_user,
					to_users: webhook.webhook.to_users
				},
			});

			if (!options.keepWebhooks) {
				// Pr. Default we will remove webhooks
				await WebhookQueue.collection.remove({
					_id: webhook._id,
					errMsg: {
						$exists: false
					}
				});
			} else {
				// Update the webhook
				await WebhookQueue.collection.updateOne({
					_id: webhook._id
				}, {
					$set: {
						// Mark as sent
						sent: true,
						// Set the sent date
						sentAt: new Date(),
						// Not being sent anymore
						sending: 0
					}
				});

			}
		} catch (error) {
			console.error(error);
			await WebhookQueue.collection.updateOne({
				_id: webhook._id
			}, {
				$set: {
					// error message
					errMsg: error
				}
			});
		}

	}

	// Universal send function
	var _querySend = async function (options) {

		if (self.sendWebhook) {
			await self.sendWebhook(options);
		}

		return {
			webhook: [options._id]
		};
	};

	self.serverSend = async function (options) {
		options = options || {};
		return await _querySend(options);
	};


	// This interval will allow only one webhook to be sent at a time, it
	// will check for new webhooks at every `options.sendInterval`
	// (default interval is 15000 ms)
	//
	// It looks in webhooks collection to see if theres any pending
	// webhooks, if so it will try to reserve the pending webhook.
	// If successfully reserved the send is started.
	//
	// If webhook.query is type string, it's assumed to be a json string
	// version of the query selector. Making it able to carry `$` properties in
	// the mongo collection.
	//
	// Pr. default webhooks are removed from the collection after send have
	// completed. Setting `options.keepWebhooks` will update and keep the
	// webhook eg. if needed for historical reasons.
	//
	// After the send have completed a "send" event will be emitted with a
	// status object containing webhook id and the send result object.
	//
	var isSendingWebhook = false;

	if (options.sendInterval !== null) {

		// This will require index since we sort webhooks by createdAt
		WebhookQueue.collection.createIndex({
			createdAt: 1
		});
		WebhookQueue.collection.createIndex({
			sent: 1
		});
		WebhookQueue.collection.createIndex({
			sending: 1
		});


		var sendWebhook = async function (webhook) {
			// Reserve webhook
			var now = +new Date();
			var timeoutAt = now + options.sendTimeout;
			var reserved = await WebhookQueue.collection.updateOne({
				_id: webhook._id,
				sent: false, // xxx: need to make sure this is set on create
				sending: {
					$lt: now
				}
			}, {
				$set: {
					sending: timeoutAt,
				}
			});

			// Make sure we only handle webhooks reserved by this
			// instance
			if (reserved) {

				// Send the webhook
				await WebhookQueue.serverSend(webhook);

			} // Else could not reserve
		}; // EO sendWebhook

		await sendWorker(async function () {

			if (isSendingWebhook) {
				return;
			}
			// Set send fence
			isSendingWebhook = true;

			var batchSize = options.sendBatchSize || 1;

			var now = +new Date();

			// Find webhooks that are not being or already sent
			var pendingWebhooks = await WebhookQueue.collection.find({
				$and: [
					// Message is not sent
					{
						sent: false
					},
					// And not being sent by other instances
					{
						sending: {
							$lt: now
						}
					},
					// And no error
					{
						errMsg: {
							$exists: false
						}
					}
				]
			}).sort({
				createdAt: 1
			}).limit(batchSize).toArray();

			for (const webhook of pendingWebhooks) {
				try {
					await sendWebhook(webhook);
				} catch (error) {
					console.error(error.stack);
					console.log('WebhookQueue: Could not send doc id: "' + webhook._id + '", Error: ' + error.message);
					await WebhookQueue.collection.updateOne({
						_id: webhook._id
					}, {
						$set: {
							// error message
							errMsg: error.message
						}
					});
				}
			}; // EO forEach

			// Remove the send fence
			isSendingWebhook = false;
		}, options.sendInterval || 15000); // Default every 15th sec

	} else {
		if (WebhookQueue.debug) {
			console.log('WebhookQueue: Send server is disabled');
		}
	}

};

module.exports = WebhookQueue;