"use strict";
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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.destroy = exports.executeV2 = exports.executeV1 = exports.preview = exports.find = exports.save = exports.import = exports.fetch = void 0;
const utils_1 = require("../../../db/utils");
const constants_1 = require("../../../constants");
const threads_1 = require("../../../threads");
const datasource_1 = require("../datasource");
const import_1 = require("./import");
const utils_2 = require("../../../threads/utils");
const environment_1 = require("../../../environment");
const context_1 = require("@devlego/backend-core/context");
const pro_1 = require("@devlego/pro");
const backend_core_1 = require("@devlego/backend-core");
const utils_3 = require("@devlego/backend-core/utils");
const constants_2 = require("@devlego/backend-core/constants");
const Runner = new threads_1.Thread(threads_1.ThreadType.QUERY, {
    timeoutMs: environment_1.QUERY_THREAD_TIMEOUT || 10000,
});
// simple function to append "readable" to all read queries
function enrichQueries(input) {
    const wasArray = Array.isArray(input);
    const queries = wasArray ? input : [input];
    for (let query of queries) {
        if (query.queryVerb === constants_1.BaseQueryVerbs.READ) {
            query.readable = true;
        }
    }
    return wasArray ? queries : queries[0];
}
function fetch(ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const db = (0, context_1.getAppDB)();
        const body = yield db.allDocs((0, utils_1.getQueryParams)(null, {
            include_docs: true,
        }));
        ctx.body = enrichQueries(body.rows.map((row) => row.doc));
    });
}
exports.fetch = fetch;
const _import = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
    const body = ctx.request.body;
    const data = body.data;
    const importer = new import_1.RestImporter(data);
    yield importer.init();
    let datasourceId;
    if (!body.datasourceId) {
        // construct new datasource
        const info = yield importer.getInfo();
        let datasource = {
            type: "datasource",
            source: "REST",
            config: {
                url: info.url,
                defaultHeaders: [],
            },
            name: info.name,
        };
        // save the datasource
        const datasourceCtx = Object.assign({}, ctx);
        datasourceCtx.request.body.datasource = datasource;
        yield (0, datasource_1.save)(datasourceCtx);
        datasourceId = datasourceCtx.body.datasource._id;
    }
    else {
        // use existing datasource
        datasourceId = body.datasourceId;
    }
    const importResult = yield importer.importQueries(datasourceId);
    ctx.body = Object.assign(Object.assign({}, importResult), { datasourceId });
    ctx.status = 200;
});
exports.import = _import;
function save(ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const db = (0, context_1.getAppDB)();
        const query = ctx.request.body;
        const datasource = yield db.get(query.datasourceId);
        let eventFn;
        if (!query._id) {
            query._id = (0, utils_1.generateQueryID)(query.datasourceId);
            eventFn = () => backend_core_1.events.query.created(datasource, query);
        }
        else {
            eventFn = () => backend_core_1.events.query.updated(datasource, query);
        }
        const response = yield db.put(query);
        yield eventFn();
        query._rev = response.rev;
        ctx.body = query;
        ctx.message = `Query ${query.name} saved successfully.`;
    });
}
exports.save = save;
function find(ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const db = (0, context_1.getAppDB)();
        const query = enrichQueries(yield db.get(ctx.params.queryId));
        // remove properties that could be dangerous in real app
        if ((0, utils_1.isProdAppID)(ctx.appId)) {
            delete query.fields;
            delete query.parameters;
        }
        ctx.body = query;
    });
}
exports.find = find;
//Required to discern between OIDC OAuth config entries
function getOAuthConfigCookieId(ctx) {
    if (ctx.user.providerType === constants_2.Configs.OIDC) {
        return (0, utils_3.getCookie)(ctx, constants_2.Cookies.OIDC_CONFIG);
    }
}
function getAuthConfig(ctx) {
    const authCookie = (0, utils_3.getCookie)(ctx, constants_2.Cookies.Auth);
    let authConfigCtx = {};
    authConfigCtx["configId"] = getOAuthConfigCookieId(ctx);
    authConfigCtx["sessionId"] = authCookie ? authCookie.sessionId : null;
    return authConfigCtx;
}
function preview(ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const db = (0, context_1.getAppDB)();
        const datasource = yield db.get(ctx.request.body.datasourceId);
        const query = ctx.request.body;
        // preview may not have a queryId as it hasn't been saved, but if it does
        // this stops dynamic variables from calling the same query
        const { fields, parameters, queryVerb, transformer, queryId } = query;
        const authConfigCtx = getAuthConfig(ctx);
        try {
            const runFn = () => Runner.run({
                appId: ctx.appId,
                datasource,
                queryVerb,
                fields,
                parameters,
                transformer,
                queryId,
                ctx: {
                    user: ctx.user,
                    auth: Object.assign({}, authConfigCtx),
                },
            });
            const { rows, keys, info, extra } = yield pro_1.quotas.addQuery(runFn);
            const schemaFields = {};
            if ((rows === null || rows === void 0 ? void 0 : rows.length) > 0) {
                for (let key of [...new Set(keys)]) {
                    const field = rows[0][key];
                    let type = typeof field, fieldType = constants_1.FieldTypes.STRING;
                    if (field)
                        switch (type) {
                            case "boolean":
                                schemaFields[key] = constants_1.FieldTypes.BOOLEAN;
                                break;
                            case "object":
                                if (field instanceof Date) {
                                    fieldType = constants_1.FieldTypes.DATETIME;
                                }
                                else if (Array.isArray(field)) {
                                    fieldType = constants_1.FieldTypes.ARRAY;
                                }
                                else {
                                    fieldType = constants_1.FieldTypes.JSON;
                                }
                                break;
                            case "number":
                                fieldType = constants_1.FieldTypes.NUMBER;
                                break;
                        }
                    schemaFields[key] = fieldType;
                }
            }
            yield backend_core_1.events.query.previewed(datasource, query);
            ctx.body = {
                rows,
                schemaFields,
                info,
                extra,
            };
        }
        catch (err) {
            ctx.throw(400, err);
        }
    });
}
exports.preview = preview;
function execute(ctx, opts = { rowsOnly: false, isAutomation: false }) {
    return __awaiter(this, void 0, void 0, function* () {
        const db = (0, context_1.getAppDB)();
        const query = yield db.get(ctx.params.queryId);
        const datasource = yield db.get(query.datasourceId);
        let authConfigCtx = {};
        if (!opts.isAutomation) {
            authConfigCtx = getAuthConfig(ctx);
        }
        const enrichedParameters = ctx.request.body.parameters || {};
        // make sure parameters are fully enriched with defaults
        if (query && query.parameters) {
            for (let parameter of query.parameters) {
                if (!enrichedParameters[parameter.name]) {
                    enrichedParameters[parameter.name] = parameter.default;
                }
            }
        }
        // call the relevant CRUD method on the integration class
        try {
            const runFn = () => Runner.run({
                appId: ctx.appId,
                datasource,
                queryVerb: query.queryVerb,
                fields: query.fields,
                pagination: ctx.request.body.pagination,
                parameters: enrichedParameters,
                transformer: query.transformer,
                queryId: ctx.params.queryId,
                ctx: {
                    user: ctx.user,
                    auth: Object.assign({}, authConfigCtx),
                },
            });
            const { rows, pagination, extra } = yield pro_1.quotas.addQuery(runFn);
            if (opts && opts.rowsOnly) {
                ctx.body = rows;
            }
            else {
                ctx.body = Object.assign({ data: rows, pagination }, extra);
            }
        }
        catch (err) {
            ctx.throw(400, err);
        }
    });
}
function executeV1(ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        return execute(ctx, { rowsOnly: true, isAutomation: false });
    });
}
exports.executeV1 = executeV1;
function executeV2(ctx, { isAutomation } = {}) {
    return __awaiter(this, void 0, void 0, function* () {
        return execute(ctx, { rowsOnly: false, isAutomation });
    });
}
exports.executeV2 = executeV2;
const removeDynamicVariables = (queryId) => __awaiter(void 0, void 0, void 0, function* () {
    const db = (0, context_1.getAppDB)();
    const query = yield db.get(queryId);
    const datasource = yield db.get(query.datasourceId);
    const dynamicVariables = datasource.config.dynamicVariables;
    if (dynamicVariables) {
        // delete dynamic variables from the datasource
        datasource.config.dynamicVariables = dynamicVariables.filter((dv) => dv.queryId !== queryId);
        yield db.put(datasource);
        // invalidate the deleted variables
        const variablesToDelete = dynamicVariables.filter((dv) => dv.queryId === queryId);
        yield (0, utils_2.invalidateDynamicVariables)(variablesToDelete);
    }
});
function destroy(ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const db = (0, context_1.getAppDB)();
        const queryId = ctx.params.queryId;
        yield removeDynamicVariables(queryId);
        const query = yield db.get(queryId);
        const datasource = yield db.get(query.datasourceId);
        yield db.remove(ctx.params.queryId, ctx.params.revId);
        ctx.message = `Query deleted.`;
        ctx.status = 200;
        yield backend_core_1.events.query.deleted(datasource, query);
    });
}
exports.destroy = destroy;
