"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.cleanupAutomations = exports.checkForWebhooks = exports.enableCronTrigger = exports.disableAllCrons = exports.removeDeprecated = exports.updateTestHistory = exports.processEvent = void 0;
const threads_1 = require("../threads");
const triggerInfo_1 = require("./triggerInfo");
const webhooks = __importStar(require("../api/controllers/webhook"));
const bullboard_1 = require("./bullboard");
const newid_1 = __importDefault(require("../db/newid"));
const utilities_1 = require("../utilities");
const constants_1 = require("../constants");
const db_1 = require("@devlego/backend-core/db");
const fp_1 = require("lodash/fp");
const context_1 = require("@devlego/backend-core/context");
const backend_core_1 = require("@devlego/backend-core");
const pro_1 = require("@devlego/pro");
const WH_STEP_ID = triggerInfo_1.definitions.WEBHOOK.stepId;
const CRON_STEP_ID = triggerInfo_1.definitions.CRON.stepId;
const Runner = new threads_1.Thread(threads_1.ThreadType.AUTOMATION);
function processEvent(job) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            console.log(`${job.data.automation.appId} automation ${job.data.automation._id} running`);
            // need to actually await these so that an error can be captured properly
            const tenantId = backend_core_1.tenancy.getTenantIDFromAppID(job.data.event.appId);
            return yield backend_core_1.tenancy.doInTenant(tenantId, () => __awaiter(this, void 0, void 0, function* () {
                const runFn = () => Runner.run(job);
                return pro_1.quotas.addAutomation(runFn);
            }));
        }
        catch (err) {
            const errJson = JSON.stringify(err);
            console.error(`${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${errJson}`);
            console.trace(err);
            return { err };
        }
    });
}
exports.processEvent = processEvent;
function updateTestHistory(appId, automation, history) {
    return __awaiter(this, void 0, void 0, function* () {
        return (0, utilities_1.updateEntityMetadata)(constants_1.MetadataTypes.AUTOMATION_TEST_HISTORY, automation._id, (metadata) => {
            if (metadata && Array.isArray(metadata.history)) {
                metadata.history.push(history);
            }
            else {
                metadata = {
                    history: [history],
                };
            }
            return metadata;
        });
    });
}
exports.updateTestHistory = updateTestHistory;
function removeDeprecated(definitions) {
    const base = (0, fp_1.cloneDeep)(definitions);
    for (let key of Object.keys(base)) {
        if (base[key].deprecated) {
            delete base[key];
        }
    }
    return base;
}
exports.removeDeprecated = removeDeprecated;
// end the repetition and the job itself
function disableAllCrons(appId) {
    return __awaiter(this, void 0, void 0, function* () {
        const promises = [];
        const jobs = yield bullboard_1.queue.getRepeatableJobs();
        for (let job of jobs) {
            if (job.key.includes(`${appId}_cron`)) {
                promises.push(bullboard_1.queue.removeRepeatableByKey(job.key));
                if (job.id) {
                    promises.push(bullboard_1.queue.removeJobs(job.id));
                }
            }
        }
        return Promise.all(promises);
    });
}
exports.disableAllCrons = disableAllCrons;
/**
 * This function handles checking of any cron jobs that need to be enabled/updated.
 * @param {string} appId The ID of the app in which we are checking for webhooks
 * @param {object|undefined} automation The automation object to be updated.
 */
function enableCronTrigger(appId, automation) {
    return __awaiter(this, void 0, void 0, function* () {
        const trigger = automation ? automation.definition.trigger : null;
        function isCronTrigger(auto) {
            return (auto &&
                auto.definition.trigger &&
                auto.definition.trigger.stepId === CRON_STEP_ID);
        }
        // need to create cron job
        if (isCronTrigger(automation) && (trigger === null || trigger === void 0 ? void 0 : trigger.inputs.cron)) {
            // make a job id rather than letting Bull decide, makes it easier to handle on way out
            const jobId = `${appId}_cron_${(0, newid_1.default)()}`;
            const job = yield bullboard_1.queue.add({
                automation,
                event: { appId, timestamp: Date.now() },
            }, { repeat: { cron: trigger.inputs.cron }, jobId });
            // Assign cron job ID from bull so we can remove it later if the cron trigger is removed
            trigger.cronJobId = job.id;
            // can't use getAppDB here as this is likely to be called from dev app,
            // but this call could be for dev app or prod app, need to just use what
            // was passed in
            yield (0, db_1.doWithDB)(appId, (db) => __awaiter(this, void 0, void 0, function* () {
                const response = yield db.put(automation);
                automation._id = response.id;
                automation._rev = response.rev;
            }));
        }
        return automation;
    });
}
exports.enableCronTrigger = enableCronTrigger;
/**
 * This function handles checking if any webhooks need to be created or deleted for automations.
 * @param {string} appId The ID of the app in which we are checking for webhooks
 * @param {object|undefined} oldAuto The old automation object if updating/deleting
 * @param {object|undefined} newAuto The new automation object if creating/updating
 * @returns {Promise<object|undefined>} After this is complete the new automation object may have been updated and should be
 * written to DB (this does not write to DB as it would be wasteful to repeat).
 */
function checkForWebhooks({ oldAuto, newAuto }) {
    return __awaiter(this, void 0, void 0, function* () {
        const appId = (0, context_1.getAppId)();
        const oldTrigger = oldAuto ? oldAuto.definition.trigger : null;
        const newTrigger = newAuto ? newAuto.definition.trigger : null;
        const triggerChanged = oldTrigger && newTrigger && oldTrigger.id !== newTrigger.id;
        function isWebhookTrigger(auto) {
            return (auto &&
                auto.definition.trigger &&
                auto.definition.trigger.stepId === WH_STEP_ID);
        }
        // need to delete webhook
        if (isWebhookTrigger(oldAuto) &&
            (!isWebhookTrigger(newAuto) || triggerChanged) &&
            oldTrigger.webhookId) {
            try {
                let db = (0, context_1.getAppDB)();
                // need to get the webhook to get the rev
                const webhook = yield db.get(oldTrigger.webhookId);
                const ctx = {
                    appId,
                    params: { id: webhook._id, rev: webhook._rev },
                };
                // might be updating - reset the inputs to remove the URLs
                if (newTrigger) {
                    delete newTrigger.webhookId;
                    newTrigger.inputs = {};
                }
                yield webhooks.destroy(ctx);
            }
            catch (err) {
                // don't worry about not being able to delete, if it doesn't exist all good
            }
        }
        // need to create webhook
        if ((!isWebhookTrigger(oldAuto) || triggerChanged) &&
            isWebhookTrigger(newAuto)) {
            const ctx = {
                appId,
                request: {
                    body: new webhooks.Webhook("Automation webhook", constants_1.WebhookType.AUTOMATION, newAuto._id),
                },
            };
            yield webhooks.save(ctx);
            const id = ctx.body.webhook._id;
            newTrigger.webhookId = id;
            // the app ID has to be development for this endpoint
            // it can only be used when building the app
            // but the trigger endpoint will always be used in production
            const prodAppId = (0, db_1.getProdAppID)(appId);
            newTrigger.inputs = {
                schemaUrl: `api/webhooks/schema/${appId}/${id}`,
                triggerUrl: `api/webhooks/trigger/${prodAppId}/${id}`,
            };
        }
        return newAuto;
    });
}
exports.checkForWebhooks = checkForWebhooks;
/**
 * When removing an app/unpublishing it need to make sure automations are cleaned up (cron).
 * @param appId {string} the app that is being removed.
 * @return {Promise<void>} clean is complete if this succeeds.
 */
function cleanupAutomations(appId) {
    return __awaiter(this, void 0, void 0, function* () {
        yield disableAllCrons(appId);
    });
}
exports.cleanupAutomations = cleanupAutomations;
