"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GrafanaProxy = void 0;
const Config_1 = require("../Config");
const Crypt_1 = require("../Crypt");
const openflow_api_1 = require("@openiap/openflow-api");
const OAuthProvider_1 = require("../OAuthProvider");
const OAuthServer = require("oauth2-server");
const url = require("url");
const Auth_1 = require("../Auth");
const Logger_1 = require("../Logger");
const Request = OAuthServer.Request;
const Response = OAuthServer.Response;
const template = [
    '====BEGIN LICENSE====',
    '{{&licenseVersion}}',
    '{{&email}}',
    '{{&expirationDate}}',
    '{{&serial}}',
    '=====END LICENSE====='
].join('\n');
class GrafanaProxy {
    static parseJson(txt, reviver, context) {
        context = context || 20;
        try {
            return JSON.parse(txt, reviver);
        }
        catch (e) {
            if (typeof txt !== "string") {
                const isEmptyArray = Array.isArray(txt) && txt.length === 0;
                const errorMessage = "Cannot parse " +
                    (isEmptyArray ? "an empty array" : String(txt));
                throw new TypeError(errorMessage);
            }
            const syntaxErr = e.message.match(/^Unexpected token.*position\s+(\d+)/i);
            const errIdx = syntaxErr
                ? +syntaxErr[1]
                : e.message.match(/^Unexpected end of JSON.*/i)
                    ? txt.length - 1
                    : null;
            if (errIdx != null) {
                const start = errIdx <= context
                    ? 0
                    : errIdx - context;
                const end = errIdx + context >= txt.length
                    ? txt.length
                    : errIdx + context;
                e.message += ` while parsing near "${start === 0 ? "" : "..."}${txt.slice(start, end)}${end === txt.length ? "" : "..."}"`;
            }
            else {
                e.message += ` while parsing "${txt.slice(0, context * 2)}"`;
            }
            throw e;
        }
    }
    static async configure(app, parent) {
        const span = Logger_1.Logger.otel.startSubSpan("grafanaproxy.configure", parent);
        try {
            try {
                Logger_1.Logger.License.validate();
            }
            catch (error) {
                Logger_1.Logger.instanse.debug("grafana-proxy", "configure", error.message ? error.message : error);
            }
            if (!Logger_1.Logger.License.validlicense)
                return;
            app.all("/grafana", (req, res, next) => {
                this.logRequest(req.body, "/");
                if (!Logger_1.Logger.License.validlicense) {
                    var result = { message: Logger_1.Logger.License.licenserror, statusCode: 500 };
                    Logger_1.Logger.instanse.error("grafana-proxy", "configure", Logger_1.Logger.License.licenserror);
                    var json = JSON.stringify(result);
                    res.status(500).send(json); // All HTTP requests must have a response, so let's send back an error with its status code and message
                    return;
                }
                if (!Config_1.Config.db.isConnected) {
                    Logger_1.Logger.instanse.error("grafana-proxy", "configure", "OpenFlow not connected to DB");
                    res.send({
                        status: "error",
                        display_status: "Error",
                        message: "OpenFlow not connected to DB"
                    });
                }
                else {
                    Logger_1.Logger.instanse.verbose("grafana-proxy", "configure", "MongoDB Connection test OK");
                    res.send({
                        status: "success",
                        display_status: "Success",
                        message: "MongoDB Connection test OK"
                    });
                }
                // next()
            });
            let ot_grafana_search = null;
            let ot_grafana_query = null;
            if (!openflow_api_1.NoderedUtil.IsNullUndefinded(Logger_1.Logger.otel) && !openflow_api_1.NoderedUtil.IsNullUndefinded(Logger_1.Logger.otel.meter)) {
                ot_grafana_search = Logger_1.Logger.otel.meter.createHistogram('openflow_http_grafana_search_request_duration', {
                    description: 'Duration of Grafana Search requests'
                });
                ot_grafana_query = Logger_1.Logger.otel.meter.createHistogram('openflow_http_grafana_query_request_duration', {
                    description: 'Duration of Grafana Queries'
                });
            }
            app.all("/grafana/search", async (req, res, next) => {
                const span = Logger_1.Logger.otel.startSpan("grafana.search");
                try {
                    this.logRequest(req.body, "/search");
                    if (!Logger_1.Logger.License.validlicense) {
                        var result = { message: Logger_1.Logger.License.licenserror, statusCode: 500 };
                        Logger_1.Logger.instanse.error("grafana-proxy", "/grafana/search", Logger_1.Logger.License.licenserror);
                        var json = JSON.stringify(result);
                        res.status(500).send(json); // All HTTP requests must have a response, so let's send back an error with its status code and message
                        return;
                    }
                    const ot_end = Logger_1.Logger.otel.startTimer();
                    const route = url.parse(req.url).pathname;
                    // let jwt: string = await this.GetJWT(req, res);
                    // if (jwt == null) {
                    //     var json = JSON.stringify({ message: "Please login again" });
                    //     return res.status(500).send(json);
                    // }
                    let jwt = null;
                    let openflowusername = (req.body.options && req.body.options.openflowusername ? req.body.options.openflowusername : "");
                    if (openflow_api_1.NoderedUtil.IsNullEmpty(openflowusername))
                        openflowusername = (req.body.securedata && req.body.securedata.openflowusername ? req.body.securedata.openflowusername : "");
                    const openflowpassword = (req.body.securedata && req.body.securedata.openflowpassword ? req.body.securedata.openflowpassword : "");
                    if (req.headers && req.headers["authorization"]) {
                        jwt = await this.GetJWT(req, res, span);
                    }
                    else if (!openflow_api_1.NoderedUtil.IsNullEmpty(openflowusername) && !openflow_api_1.NoderedUtil.IsNullEmpty(openflowpassword)) {
                        const xgrafanauser = (req.headers ? req.headers["X-Grafana-User"] : "");
                        if (!openflow_api_1.NoderedUtil.IsNullEmpty(xgrafanauser) && xgrafanauser != openflowusername) {
                            var json = JSON.stringify({ message: "Please login again" });
                            return res.status(500).send(json);
                        }
                        let tuser = null;
                        let user = await Auth_1.Auth.ValidateByPassword(openflowusername, openflowpassword, span);
                        // let user: User = Auth.getUser(openflowusername + ":" + openflowpassword, "grafana");
                        // if (NoderedUtil.IsNullUndefinded(user)) {
                        //     user = await Auth.ValidateByPassword(openflowusername, openflowpassword, span);
                        //     if (!NoderedUtil.IsNullUndefinded(user)) {
                        //         await Auth.AddUser(user, openflowusername + ":" + openflowpassword, "grafana");
                        //     }
                        // }
                        if (user != null)
                            tuser = openflow_api_1.TokenUser.From(user);
                        if (tuser != null)
                            jwt = Crypt_1.Crypt.createToken(tuser, Config_1.Config.shorttoken_expires_in);
                    }
                    else {
                        jwt = await this.GetJWT(req, res, span);
                    }
                    if (jwt == null) {
                        var json = JSON.stringify({ message: "Please login again" });
                        Logger_1.Logger.instanse.error("grafana-proxy", "/grafana/search", "Please login again");
                        return res.status(500).send(json);
                    }
                    // Generate an id to track requests
                    const requestId = ++this.requestIdCounter;
                    // Add state for the queries in this request
                    var queryStates = [];
                    this.requestsPending[requestId] = queryStates;
                    try {
                        const query = (req.body.target ? req.body.target : (Array.isArray(req.body.queries) ? req.body.queries[0] : req.body.queries));
                        if (req.body.target == "getcollections") {
                            let result = await Config_1.Config.db.ListCollections(jwt);
                            result = result.filter(x => !x.name.endsWith("_hist"));
                            result = result.filter(x => x.name != "fs.chunks");
                            result = result.filter(x => x.name != "fs.files");
                            result = result.filter(x => x.name != "uploads.files");
                            result = result.filter(x => x.name != "uploads.chunks");
                            delete this.requestsPending[requestId];
                            let output = result.map(x => x.name);
                            res.json(output);
                            return;
                        }
                        let substitutions = {
                            "$from": new Date(req.body.from),
                            "$to": new Date(req.body.to),
                            "$dateBucketCount": this.getBucketCount(req.body.from, req.body.to, req.body.intervalMs)
                        };
                        if (req.body.optionalOptions && req.body.optionalOptions.range && req.body.optionalOptions.range.from) {
                            substitutions = {
                                "$from": new Date(req.body.optionalOptions.range.from),
                                "$to": new Date(req.body.optionalOptions.range.to),
                                "$dateBucketCount": this.getBucketCount(req.body.optionalOptions.range.from, req.body.optionalOptions.range.to, req.body.optionalOptions.range.intervalMs)
                            };
                            if (req.body.optionalOptions.range.from.indexOf("z") === -1 && req.body.optionalOptions.range.from.indexOf("Z") === -1 && req.body.optionalOptions.range.from.indexOf("T") === -1) {
                                var i1 = parseInt(req.body.range.from);
                                var i2 = parseInt(req.body.range.to);
                                substitutions.$from = new Date(parseInt(req.body.range.from));
                                substitutions.$to = new Date(parseInt(req.body.range.to));
                                substitutions.$dateBucketCount = this.getBucketCount(substitutions.$from, substitutions.$to, req.body.intervalMs);
                            }
                        }
                        // Parse query string in target
                        let queryArgs = this.parseQuery(query.query ? query.query : query, substitutions, query);
                        if (queryArgs.err != null) {
                            this.queryError(requestId, queryArgs.err, res, next);
                            return;
                        }
                        else {
                            this.doTemplateQuery(requestId, queryArgs, req.body.db, jwt, req, res, next, span);
                        }
                    }
                    catch (error) {
                        this.queryError(requestId, error, res, next);
                    }
                    Logger_1.Logger.otel.endTimer(ot_end, ot_grafana_search, { route, code: res.statusCode.toString(), method: req.method });
                }
                catch (error) {
                    span.recordException(error);
                    throw error;
                }
                finally {
                    Logger_1.Logger.otel.endSpan(span);
                }
            });
            // Called to get graph points
            app.all("/grafana/query", async (req, res, next) => {
                const span = Logger_1.Logger.otel.startSpan("grafana.query");
                try {
                    this.logRequest(req.body, "/query");
                    if (!Logger_1.Logger.License.validlicense) {
                        var result = { message: Logger_1.Logger.License.licenserror, statusCode: 500 };
                        Logger_1.Logger.instanse.error("grafana-proxy", "/grafana/query", Logger_1.Logger.License.licenserror);
                        var json = JSON.stringify(result);
                        res.status(500).send(json); // All HTTP requests must have a response, so let's send back an error with its status code and message
                        span.recordException(Logger_1.Logger.License.licenserror);
                        return;
                    }
                    const ot_end = Logger_1.Logger.otel.startTimer();
                    const route = url.parse(req.url).pathname;
                    if (openflow_api_1.NoderedUtil.IsNullUndefinded(req.body.range)) {
                        res.send({
                            status: "success",
                            display_status: "Success",
                            message: "MongoDB Connection test OK"
                        });
                        Logger_1.Logger.instanse.debug("grafana-proxy", "/grafana/query", "MongoDB Connection test OK");
                        // next();
                        return;
                    }
                    // Parse query string in target
                    let substitutions = {
                        "$from": new Date(req.body.range.from),
                        "$to": new Date(req.body.range.to),
                        "$dateBucketCount": this.getBucketCount(req.body.range.from, req.body.range.to, req.body.intervalMs)
                    };
                    if (req.body.range.from.indexOf("z") === -1 && req.body.range.from.indexOf("Z") === -1 && req.body.range.from.indexOf("T") === -1) {
                        var i1 = parseInt(req.body.range.from);
                        var i2 = parseInt(req.body.range.to);
                        substitutions.$from = new Date(parseInt(req.body.range.from));
                        substitutions.$to = new Date(parseInt(req.body.range.to));
                        substitutions.$dateBucketCount = this.getBucketCount(substitutions.$from, substitutions.$to, req.body.intervalMs);
                    }
                    let jwt = null;
                    let openflowusername = (req.body.options && req.body.options.openflowusername ? req.body.options.openflowusername : "");
                    if (openflow_api_1.NoderedUtil.IsNullEmpty(openflowusername))
                        openflowusername = (req.body.securedata && req.body.securedata.openflowusername ? req.body.securedata.openflowusername : "");
                    const openflowpassword = (req.body.securedata && req.body.securedata.openflowpassword ? req.body.securedata.openflowpassword : "");
                    if (req.headers && req.headers["authorization"]) {
                        jwt = await this.GetJWT(req, res, span);
                    }
                    else if (!openflow_api_1.NoderedUtil.IsNullEmpty(openflowusername) && !openflow_api_1.NoderedUtil.IsNullEmpty(openflowpassword)) {
                        const xgrafanauser = (req.headers ? req.headers["X-Grafana-User"] : "");
                        if (!openflow_api_1.NoderedUtil.IsNullEmpty(xgrafanauser) && xgrafanauser != openflowusername) {
                            var json = JSON.stringify({ message: "Please login again" });
                            span.recordException("Please login again");
                            return res.status(500).send(json);
                        }
                        let tuser = null;
                        let user = await Auth_1.Auth.ValidateByPassword(openflowusername, openflowpassword, span);
                        // let user: User = Auth.getUser(openflowusername + ":" + openflowpassword, "grafana");
                        // if (NoderedUtil.IsNullUndefinded(user)) {
                        //     user = await Auth.ValidateByPassword(openflowusername, openflowpassword, span);
                        //     if (!NoderedUtil.IsNullUndefinded(user)) {
                        //         await Auth.AddUser(user, openflowusername + ":" + openflowpassword, "grafana");
                        //     }
                        // }
                        if (user != null)
                            tuser = openflow_api_1.TokenUser.From(user);
                        if (tuser != null)
                            jwt = Crypt_1.Crypt.createToken(tuser, Config_1.Config.shorttoken_expires_in);
                    }
                    else {
                        jwt = await this.GetJWT(req, res, span);
                    }
                    if (jwt == null) {
                        var json = JSON.stringify({ message: "Please login again" });
                        Logger_1.Logger.instanse.error("grafana-proxy", "/grafana/query", "Please login again");
                        span.recordException("Please login again");
                        return res.status(500).send(json);
                    }
                    // Generate an id to track requests
                    const requestId = ++this.requestIdCounter;
                    // Add state for the queries in this request
                    var queryStates = [];
                    this.requestsPending[requestId] = queryStates;
                    var error = false;
                    req.body.targets = req.body.targets.filter(x => x.hide != true);
                    for (var queryId = 0; queryId < req.body.targets.length && !error; queryId++) {
                        const tg = req.body.targets[queryId];
                        if (tg.intervalMs == null || tg.intervalMs == "") {
                            tg.intervalMs = 1000;
                        }
                        if (tg.maxDataPoints == null || tg.maxDataPoints == "") {
                            tg.maxDataPoints = 100;
                        }
                        substitutions["$intervalMs"] = tg.intervalMs;
                        substitutions["$maxDataPoints"] = tg.maxDataPoints;
                        let query = tg.target;
                        // if (query == null && tg.queryText != null) query = tg.queryText;
                        if (tg.queryText != null)
                            query = tg.queryText;
                        let queryArgs = this.parseQuery(query, substitutions, tg);
                        queryArgs.type = tg.type;
                        queryArgs.refId = tg.refId;
                        if (tg.queryType)
                            queryArgs.type = tg.queryType;
                        if (queryArgs.err != null) {
                            span.recordException(queryArgs.err);
                            this.queryError(requestId, queryArgs.err, res, next);
                            error = true;
                            return;
                        }
                        else {
                            // Add to the state
                            queryStates.push({ pending: true, refId: tg.refId, type: queryArgs.type });
                            // Run the query
                            this.runAggregateQuery(requestId, queryId, req.body, queryArgs, jwt, req, res, next, span);
                        }
                    }
                    if (req.body.targets.length == 0) {
                        res.json([]);
                    }
                    Logger_1.Logger.otel.endTimer(ot_end, ot_grafana_query, { route, code: res.statusCode.toString(), method: req.method });
                }
                catch (error) {
                    span.recordException(error);
                    throw error;
                }
                finally {
                    Logger_1.Logger.otel.endSpan(span);
                }
            });
        }
        catch (error) {
            span === null || span === void 0 ? void 0 : span.recordException(error);
            Logger_1.Logger.instanse.error("grafana-proxy", "configure", error);
            return;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    static logRequest(body, type) {
        Logger_1.Logger.instanse.verbose("grafana-proxy", "logRequest", type);
        Logger_1.Logger.instanse.silly("grafana-proxy", "logRequest", type + ":\n" + JSON.stringify(body, null, 2));
    }
    static logQuery(query, options) {
        Logger_1.Logger.instanse.silly("grafana-proxy", "logQuery", JSON.stringify(query, null, 2));
        Logger_1.Logger.instanse.silly("grafana-proxy", "logQuery", JSON.stringify(options, null, 2));
    }
    static queryError(requestId, err, res, next) {
        // We only 1 return error per query so it may have been removed from the list
        if (requestId in this.requestsPending) {
            // Remove request
            delete this.requestsPending[requestId];
            // Send back error
            // next(err);
            // res.status(500).json(err);
            // next(new Error(err));
            var result = { message: err.message, statusCode: 500 };
            Logger_1.Logger.instanse.error("grafana-proxy", "queryError", err.message);
            if (!err.statusCode)
                err.statusCode = 500; // If err has no specified error code, set error code to "Internal Server Error (500)"
            var json = JSON.stringify(result);
            res.status(err.statusCode).send(json); // All HTTP requests must have a response, so let's send back an error with its status code and message
        }
    }
    // Called when query finished
    static queryFinished(requestId, queryId, results, res, next) {
        // We only 1 return error per query so it may have been removed from the list
        if (requestId in this.requestsPending) {
            var queryStatus = this.requestsPending[requestId];
            // Mark this as finished
            queryStatus[queryId].pending = false;
            queryStatus[queryId].results = results;
            // See if we're all done
            var done = true;
            for (var i = 0; i < queryStatus.length; i++) {
                if (queryStatus[i].pending == true) {
                    done = false;
                    break;
                }
            }
            // If query done, send back results
            if (done) {
                // Concatenate results
                let output = [];
                let count = 0;
                for (var i = 0; i < queryStatus.length; i++) {
                    const queryResults = queryStatus[i];
                    const refId = queryStatus[i].refId;
                    const type = queryStatus[i].type;
                    // const res = { "target": queryStatus[i].refId, "datapoints": queryResults.results }
                    // output.push(res)
                    if (type == "timeserie" || queryResults.results.type == "timeserie") {
                        var keys = Object.keys(queryResults.results);
                        for (var k = 0; k < keys.length; k++) {
                            var tg = keys[k];
                            // if (tg != "type") queryResults.results[tg].refId = refId;
                            // if (tg != "type") queryResults.results[tg].target = refId;
                            output.push(queryResults.results[tg]);
                            count++;
                        }
                    }
                    else {
                        queryResults.results.refId = refId;
                        // queryResults.results.Target = refId;
                        // queryResults.results.values = queryResults.results.rows;
                        output.push(queryResults.results);
                        if (queryResults.results && queryResults.results.rows) {
                            count += queryResults.results.rows.length;
                        }
                        else if (queryResults.results && queryResults.results.length) {
                            count += queryResults.results.length;
                        }
                        else {
                            count++;
                        }
                    }
                }
                Logger_1.Logger.instanse.verbose("grafana-proxy", "queryFinished", "return result for " + queryStatus.length + " queries with " + count + " results");
                res.json(output);
                // next()
                // Remove request
                delete this.requestsPending[requestId];
            }
        }
    }
    static parseQuery(query, substitutions, tg) {
        let doc = {};
        if (tg && tg.dtfield)
            doc.dtfield = tg.dtfield;
        let queryErrors = [];
        if (openflow_api_1.NoderedUtil.IsNullEmpty(query)) {
            doc.err = new Error("Query cannot be empty.");
            return doc;
        }
        query = query.trim();
        if (query.substring(0, 3) != "db.") {
            doc.err = new Error("Query must start with db.");
            Logger_1.Logger.instanse.error("grafana-proxy", "parseQuery", "Query must start with db.");
            Logger_1.Logger.instanse.debug("grafana-proxy", "parseQuery", query);
            return doc;
        }
        // Query is of the form db.<collection> . aggregate or db.<collection>.find
        // Split on the first ( after db.
        var openBracketIndex = query.indexOf("(", 3);
        if (openBracketIndex == -1) {
            queryErrors.push("Can't find opening bracket");
        }
        else {
            // Split the first bit - it's the collection name and operation ( must be aggregate )
            var parts = query.substring(3, openBracketIndex).split(".");
            // Collection names can have .s so last part is operation, rest is the collection name
            if (parts.length >= 2) {
                doc.operation = parts.pop().trim();
                doc.collection = parts.join(".");
            }
            else {
                queryErrors.push("Invalid collection and operation syntax");
            }
            query = query.substring(openBracketIndex);
            do {
                try {
                    openBracketIndex = query.indexOf("(");
                    var closeBracketIndex = query.indexOf(")", openBracketIndex);
                    var operation = query.substring(0, openBracketIndex).trim();
                    if (operation.startsWith("."))
                        operation = operation.substring(1);
                    if (openflow_api_1.NoderedUtil.IsNullEmpty(operation))
                        operation = doc.operation; // The first time, operation will be empty
                    var args = query.substring(openBracketIndex + 1, closeBracketIndex);
                    query = query.substring(closeBracketIndex + 1);
                    if (operation == "aggregate") {
                        // Wrap args in array syntax so we can check for optional options arg
                        // args = args.trim();
                        // if (!args.startsWith("[") && !args.endsWith("]")) 
                        args = "[" + args + "]";
                        // let docs = JSON.parse(args);
                        let docs = this.parseJson(args, null, null);
                        // First Arg is pipeline
                        doc.pipeline = docs[0];
                        // If we have 2 top level args, second is agg options
                        if (docs.length == 2) {
                            doc.options = docs[1];
                        }
                        // Replace with substitutions
                        for (var i = 0; i < doc.pipeline.length; i++) {
                            var stage = doc.pipeline[i];
                            this.forIn(stage, (obj, key, value) => {
                                if (typeof (value) == "string") {
                                    if (value in substitutions) {
                                        obj[key] = substitutions[value];
                                    }
                                }
                            });
                        }
                    }
                    else if (operation == "find") {
                        // Wrap args in array syntax so we can check for optional options arg
                        args = "[" + args + "]";
                        // let docs = JSON.parse(args);
                        let docs = this.parseJson(args, null, null);
                        // First Arg is pipeline
                        doc.query = docs[0];
                        if (doc.query === undefined)
                            doc.query = {};
                        // If we have 2 top level args, second is options
                        if (docs.length == 2) {
                            doc.options = docs[1];
                        }
                        // Replace with substitutions
                        var stage = doc.query;
                        this.forIn(stage, (obj, key, value) => {
                            if (typeof (value) == "string") {
                                if (value in substitutions) {
                                    obj[key] = substitutions[value];
                                }
                            }
                        });
                    }
                    else if (operation == "limit") {
                        doc.limit = args;
                    }
                    else if (operation == "skip") {
                        doc.skip = args;
                    }
                    else if (operation == "sort") {
                        doc.sort = this.parseJson(args, null, null);
                    }
                    else if (operation == "projection") {
                        doc.projection = this.parseJson(args, null, null);
                    }
                    else {
                        queryErrors.push("Unknown operation " + operation + ", only aggregate supported");
                    }
                }
                catch (error) {
                    queryErrors.push(error.message);
                    Logger_1.Logger.instanse.error("grafana-proxy", "parseQuery", args);
                }
                openBracketIndex = query.indexOf("(", 1);
            } while (openBracketIndex > -1);
        }
        if (queryErrors.length > 0) {
            doc.err = new Error("Failed to parse query - " + queryErrors.join(":"));
        }
        else {
            const dtfield = (!openflow_api_1.NoderedUtil.IsNullEmpty(doc.dtfield) ? doc.dtfield : "_created");
            // const dtfield = (!NoderedUtil.IsNullEmpty(doc.dtfield) ? doc.dtfield : "lastseen");
            if (tg.intervalMs && dtfield && doc.operation == "aggregate") {
                if (substitutions && substitutions.$from && substitutions.$to) {
                    const fromo = {};
                    fromo[dtfield] = { "$gte": substitutions.$from };
                    const too = {};
                    too[dtfield] = { "$lte": substitutions.$to };
                    const _and = { "$and": [fromo, too] };
                    if (dtfield && doc.operation == "find") {
                        if (JSON.stringify(doc.query) == "{}") {
                            doc.query = _and;
                        }
                        else {
                            doc.query = { "$and": [_and, doc.query] };
                        }
                    }
                    else if (dtfield && doc.operation == "aggregate") {
                        doc.pipeline.unshift({ "$match": _and });
                    }
                }
            }
        }
        return doc;
    }
    static forIn(obj, processFunc) {
        var key;
        for (key in obj) {
            var value = obj[key];
            processFunc(obj, key, value);
            if (value != null && typeof (value) == "object") {
                this.forIn(value, processFunc);
            }
        }
    }
    static getBucketCount(from, to, intervalMs) {
        var boundaries = [];
        var current = new Date(from).getTime();
        var toMs = new Date(to).getTime();
        var count = 0;
        while (current < toMs) {
            current += intervalMs;
            count++;
        }
        return count;
    }
    static getTableResults(docs) {
        var columns = {};
        // Build superset of columns
        for (var i = 0; i < docs.length; i++) {
            var doc = docs[i];
            // Go through all properties
            for (var propName in doc) {
                // See if we need to add a new column
                if (!(propName in columns)) {
                    columns[propName] =
                        {
                            text: propName,
                            type: "text"
                        };
                    if (typeof doc[propName] === "number")
                        columns[propName].type = "number";
                    if (typeof doc[propName] === "boolean")
                        columns[propName].type = "boolean";
                }
            }
        }
        // Build return rows
        let rows = [];
        for (var i = 0; i < docs.length; i++) {
            var doc = docs[i];
            let row = [];
            // All cols
            for (var colName in columns) {
                var col = columns[colName];
                if (col.text in doc) {
                    row.push(doc[col.text]);
                }
                else {
                    row.push(null);
                }
            }
            rows.push(row);
        }
        var results = {};
        results["table"] = {
            columns: Object.values(columns),
            rows: rows,
            type: "table"
        };
        return results["table"];
        // return results
    }
    static getTimeseriesResults(docs, queryArgs) {
        var results = {};
        for (var i = 0; i < docs.length; i++) {
            var doc = docs[i];
            var tg = null;
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(doc["name"])) {
                tg = doc["name"];
            }
            // if (!NoderedUtil.IsNullEmpty(doc["name"])) {
            //     tg = doc["name"];
            // } else if (!NoderedUtil.IsNullEmpty(doc["_id"])) {
            //     tg = doc["_id"];
            // }
            var dp = null;
            if (tg in results) {
                dp = results[tg];
            }
            else {
                dp = { "target": tg, "datapoints": [] };
                results[tg] = dp;
                results[tg] = dp;
                if (typeof tg === "number") {
                    dp.target = tg.toString();
                }
                else if (typeof tg === "string") {
                    dp.target = tg.toString();
                }
                else if (typeof tg === "boolean") {
                    dp.target = tg.toString();
                }
                else {
                    dp.target = undefined;
                }
                // if (typeof tg === "number") {
                //     dp.target = tg.toString();
                // } else if (typeof tg !== "number") {
                //     // dp.target = tg.toString();
                //     // dp.target = JSON.stringify(tg);
                //     dp.target = queryArgs.refId
                // }
                // username
            }
            var ts = null;
            var value = 1;
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(queryArgs.dtfield) && !openflow_api_1.NoderedUtil.IsNullEmpty(doc[queryArgs.dtfield])) {
                ts = doc[queryArgs.dtfield].getTime();
            }
            else if (!openflow_api_1.NoderedUtil.IsNullEmpty(doc["ts"])) {
                ts = doc["ts"].getTime();
            }
            else if (!openflow_api_1.NoderedUtil.IsNullEmpty(doc["_created"])) {
                ts = doc["_created"].getTime();
                // results[tg].datapoints.push([doc["value"], doc["_created"].getTime()])
            }
            else {
                throw new Error("Failed locating timeseries datatime as either ts or _created from result");
            }
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(doc["value"])) {
                value = parseFloat(doc["value"]);
            }
            else if (!openflow_api_1.NoderedUtil.IsNullEmpty(doc["Value"])) {
                value = parseFloat(doc["Value"]);
            }
            results[tg].datapoints.push([value, ts]);
        }
        return results;
    }
    // Runs a query to support templates. Must returns documents of the form
    // { _id : <id> }
    static async doTemplateQuery(requestId, queryArgs, db, jwt, req, res, next, parent) {
        const span = Logger_1.Logger.otel.startSubSpan("grafana.runAggregateQuery", parent);
        if (queryArgs.err == null) {
            if (!Config_1.Config.db.isConnected) {
                this.queryError(requestId, new Error("OpenFlow not connected to DB"), res, next);
                span.recordException("OpenFlow not connected to DB");
                Logger_1.Logger.otel.endSpan(span);
                return;
            }
            // Remove request from list
            if (requestId in this.requestsPending) {
                delete this.requestsPending[requestId];
            }
            // Get the documents collection
            const collectionname = queryArgs.collection;
            span.setAttribute("collectionname", collectionname);
            span.setAttribute("operation", queryArgs.operation);
            try {
                let result;
                if (queryArgs.operation == "aggregate") {
                    this.replaceinstring(queryArgs.pipeline);
                    result = await Config_1.Config.db.aggregate(queryArgs.pipeline.concat({ "$limit": 4000 }), collectionname, jwt, undefined, span);
                }
                else if (queryArgs.operation == "find") {
                    let limit = 6000, skip = 0, sort = null, projection = null;
                    this.replaceinstring(queryArgs.query);
                    if (!openflow_api_1.NoderedUtil.IsNullEmpty(queryArgs.limit))
                        limit = parseInt(queryArgs.limit);
                    if (!openflow_api_1.NoderedUtil.IsNullEmpty(queryArgs.skip))
                        skip = queryArgs.skip;
                    if (!openflow_api_1.NoderedUtil.IsNullUndefinded(queryArgs.sort))
                        sort = queryArgs.sort;
                    if (!openflow_api_1.NoderedUtil.IsNullUndefinded(queryArgs.projection))
                        projection = queryArgs.projection;
                    result = await Config_1.Config.db.query({ query: queryArgs.query, projection, top: limit, skip, orderby: sort, collectionname, jwt }, span);
                }
                else {
                    this.queryError(requestId, "unknown operation" + queryArgs.operation, res, next);
                    span.recordException("unknown operation" + queryArgs.operation);
                    Logger_1.Logger.otel.endSpan(span);
                    return;
                }
                let output = [];
                for (var i = 0; i < result.length; i++) {
                    var doc = result[i];
                    var keys = Object.keys(doc);
                    // if (doc["_id"] != null) {
                    if (keys.length == 1) {
                        // output.push(doc["_id"])
                        output.push(doc[keys[0]]);
                    }
                    else {
                        if (openflow_api_1.NoderedUtil.IsNullEmpty(doc.text) && !openflow_api_1.NoderedUtil.IsNullEmpty(doc.name)) {
                            doc.text = doc.name;
                            delete doc.name;
                        }
                        if (openflow_api_1.NoderedUtil.IsNullEmpty(doc.value) && !openflow_api_1.NoderedUtil.IsNullEmpty(doc._id)) {
                            doc.value = doc._id;
                        }
                        if (openflow_api_1.NoderedUtil.IsNullEmpty(doc.value) && !openflow_api_1.NoderedUtil.IsNullEmpty(doc.text)) {
                            doc.value = doc.text;
                        }
                        keys.forEach(key => {
                            if (key != "text" && key != "value") {
                                delete doc[key];
                            }
                        });
                        if (!openflow_api_1.NoderedUtil.IsNullEmpty(doc["text"]) || !openflow_api_1.NoderedUtil.IsNullEmpty(doc["value"]) ||
                            !openflow_api_1.NoderedUtil.IsNullUndefinded(doc["_id"])) {
                            output.push(doc);
                        }
                    }
                }
                res.json(output);
                // next()
            }
            catch (error) {
                Logger_1.Logger.instanse.error("grafana-proxy", "doTemplateQuery", error);
                this.queryError(requestId, error, res, next);
                span.recordException(error);
                Logger_1.Logger.otel.endSpan(span);
                return;
            }
        }
        else {
            // next(queryArgs.err)
            Logger_1.Logger.otel.endSpan(span);
            this.queryError(requestId, queryArgs.err, res, next);
        }
    }
    static replaceinstring(pipeline) {
        const keys = Object.keys(pipeline);
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i];
            let value = pipeline[key];
            if (key == "$in" && typeof value === "string") {
                if (value.startsWith("{")) {
                    value = value.substring(1, value.length - 1);
                }
                var array = value.split(",");
                pipeline[key] = array;
            }
            if (typeof value == "object") {
                this.replaceinstring(value);
            }
        }
    }
    // Run an aggregate query. Must return documents of the form
    // { value : 0.34334, ts : <epoch time in seconds> }
    static async runAggregateQuery(requestId, queryId, body, queryArgs, jwt, req, res, next, parent) {
        const span = Logger_1.Logger.otel.startSubSpan("grafana.runAggregateQuery", parent);
        if (!Config_1.Config.db.isConnected) {
            span.recordException("OpenFlow not connected to DB");
            Logger_1.Logger.otel.endSpan(span);
            this.queryError(requestId, new Error("OpenFlow not connected to DB"), res, next);
            return;
        }
        // Get the documents collection
        const collectionname = queryArgs.collection;
        span.setAttribute("collectionname", collectionname);
        span.setAttribute("operation", queryArgs.operation);
        try {
            let docs;
            if (queryArgs.operation == "aggregate") {
                this.logQuery(queryArgs.pipeline, queryArgs.options);
                this.replaceinstring(queryArgs.pipeline);
                docs = await Config_1.Config.db.aggregate(queryArgs.pipeline.concat({ "$limit": 4000 }), collectionname, jwt, undefined, span);
            }
            else if (queryArgs.operation == "find") {
                this.logQuery(queryArgs.query, queryArgs.options);
                this.replaceinstring(queryArgs.query);
                let limit = 6000, skip = 0, sort = null, projection = null;
                if (!openflow_api_1.NoderedUtil.IsNullEmpty(queryArgs.limit))
                    limit = parseInt(queryArgs.limit);
                if (!openflow_api_1.NoderedUtil.IsNullEmpty(queryArgs.skip))
                    skip = queryArgs.skip;
                if (!openflow_api_1.NoderedUtil.IsNullUndefinded(queryArgs.sort))
                    sort = queryArgs.sort;
                if (!openflow_api_1.NoderedUtil.IsNullUndefinded(queryArgs.projection))
                    projection = queryArgs.projection;
                docs = await Config_1.Config.db.query({ query: queryArgs.query, projection, top: limit, skip, orderby: sort, collectionname, jwt }, span);
            }
            else {
                span.recordException("unknown operation" + queryArgs.operation);
                this.queryError(requestId, "unknown operation" + queryArgs.operation, res, next);
                return;
            }
            var results = {};
            if (queryArgs.type == "timeserie") {
                results = this.getTimeseriesResults(docs, queryArgs);
            }
            else {
                results = this.getTableResults(docs);
            }
            // Mark query as finished - will send back results when all queries finished
            this.queryFinished(requestId, queryId, results, res, next);
        }
        catch (err) {
            span.recordException(err);
            this.queryError(requestId, err, res, next);
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    static async GetJWT(req, res, parent) {
        const span = Logger_1.Logger.otel.startSubSpan("grafana.GetJWT", parent);
        try {
            if (req.user) {
                let user = openflow_api_1.TokenUser.From(req.user);
                Logger_1.Logger.instanse.verbose("grafana-proxy", "GetJWT", "Resolved token as " + req.user.username);
                return Crypt_1.Crypt.createToken(user, Config_1.Config.shorttoken_expires_in);
            }
            else {
                var request = new Request(req);
                var response = new Response(res);
                var atoken = request.headers['authorization'];
                if (!openflow_api_1.NoderedUtil.IsNullEmpty(atoken)) {
                    if (atoken.indexOf(" ") > -1)
                        atoken = atoken.substring(atoken.indexOf(" ") + 1);
                    try {
                        var accesstoken = await OAuthProvider_1.OAuthProvider.instance.oidc.AccessToken.find(atoken);
                        try {
                            var expires = new Date(accesstoken.exp * 1000);
                            Logger_1.Logger.instanse.silly("grafana-proxy", "GetJWT", "grafana token expires at: " + expires);
                        }
                        catch (error) {
                        }
                        if (accesstoken != null) {
                            var res2 = await OAuthProvider_1.OAuthProvider.instance.oidc.Account.findAccount(undefined, accesstoken.accountId);
                            if (res2 != null) {
                                var user = await res2.claims(null, null, null, null);
                                if (!openflow_api_1.NoderedUtil.IsNullUndefinded(user)) {
                                    const jwt = Crypt_1.Crypt.createToken(user, "1d");
                                    return jwt;
                                }
                                else {
                                    Logger_1.Logger.instanse.error("grafana-proxy", "GetJWT", "Failed locating user with id " + accesstoken.accountId + " ????");
                                }
                            }
                        }
                    }
                    catch (error) {
                        Logger_1.Logger.instanse.error("grafana-proxy", "GetJWT", error);
                    }
                }
                var username = req.headers["X-Grafana-User"];
                if (openflow_api_1.NoderedUtil.IsNullEmpty(username))
                    username = req.headers["x-grafana-user"];
                if (!openflow_api_1.NoderedUtil.IsNullEmpty(username)) {
                    let user = await Logger_1.Logger.DBHelper.FindByUsername(username, null, span);
                    if (!openflow_api_1.NoderedUtil.IsNullUndefinded(user)) {
                        const jwt = Crypt_1.Crypt.createToken(user, "1d");
                        return jwt;
                    }
                    else {
                        Logger_1.Logger.instanse.error("grafana-proxy", "GetJWT", "Failed locating user with id " + username + " ????");
                    }
                }
            }
            return null;
        }
        catch (error) {
            Logger_1.Logger.instanse.error("grafana-proxy", "GetJWT", error);
            span.recordException(error);
            throw error;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
}
exports.GrafanaProxy = GrafanaProxy;
GrafanaProxy.requestIdCounter = 0;
GrafanaProxy.requestsPending = {};
//# sourceMappingURL=grafana-proxy.js.map