"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpParser = void 0;
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const handlebars_1 = __importDefault(require("handlebars"));
const logger_1 = __importDefault(require("../logger"));
let DELAY = 0;
/**
 * Create a parser class which defines methods to parse
 * 1. Request URL to get a matching directory
 * 2. From matched directory get .mock file content and generate a response
 */
class HttpParser {
    /**
     *
     * @param {express.Request} req Express Request object for current instance of incoming request
     * @param {express.Response} res Express response to be sent to client
     * @param {string} mockDir location of http mocks
     */
    constructor(req, res, mockDir) {
        /**
         * Finds a closest match dir for an incoming request
         * @returns {string} matchedDir for a given incoming request
         */
        this.getMatchedDir = () => {
            const reqDetails = {
                method: this.req.method.toUpperCase(),
                path: this.req.path,
                protocol: this.req.protocol,
                httpVersion: this.req.httpVersion,
                query: this.req.query,
                headers: this.req.headers,
                body: this.req.body,
            };
            const matchedDir = getWildcardPath(reqDetails.path, this.mockDir);
            return matchedDir;
        };
        /**
         * Defines a default response, if the closest matchedDir is present, parses and sends the response from mockfile,
         * Looks for API Level, and Global level default response overrides if mockfile is not found.
         * If no default response overrides are found, send the defined default response
         * @param {string} mockFile location of of the closest mached mockfile for incoming request
         * @returns {string} matchedDir for a given incoming request
         */
        this.getResponse = (mockFile) => {
            // Default response
            let response = {
                status: 404,
                body: '{"error": "Not Found"}',
                headers: {
                    "content-type": "application/json",
                },
            };
            // Check if mock file exists
            if (fs_1.default.existsSync(mockFile)) {
                this.prepareResponse(mockFile);
            }
            else {
                logger_1.default.error(`No suitable mock file found: ${mockFile}`);
                if (fs_1.default.existsSync(path_1.default.join(this.mockDir, "__", "GET.mock"))) {
                    logger_1.default.debug(`Found a custom global override for default response. Sending custom default response.`);
                    this.prepareResponse(path_1.default.join(this.mockDir, "__", "GET.mock"));
                }
                else {
                    //If no mockFile is found, return default response
                    logger_1.default.debug(`No custom global override for default response. Sending default Camouflage response.`);
                    this.res.statusCode = response.status;
                    let headerKeys = Object.keys(response.headers);
                    headerKeys.forEach((headerKey) => {
                        // @ts-ignore
                        res.setHeader(headerKey, response.headers[headerKey]);
                    });
                    this.res.send(response.body);
                }
            }
        };
        /**
         * - Since response file contains headers and body both, a PARSE_BODY flag is required to tell the logic if it's currently parsing headers or body
         * - Set responseBody to an empty string and set a default response object
         * - Set default response
         * - Compile the handlebars used in the contents of mockFile
         * - Generate actual response i.e. replace handlebars with their actual values and split the content into lines
         * - If the mockfile contains the delimiter ====, split the content using the delimiter and pick one of the responses at random
         * - Split file contents by os.EOL and read file line by line
         * - Set PARSE_BODY flag to try when reader finds a blank line, since according to standard format of a raw HTTP Response, headers and body are separated by a blank line.
         * - If line includes HTTP/HTTPS i.e. first line. Get the response status code
         * - If following conditions are met:
         *   - Line is not blank; and
         *   - Parser is not currently parsing response body yet i.e. PARSE_BODY === false
         * - Then:
         *   - Split line by :, of which first part will be header key and 2nd part will be header value
         *   - If headerKey is response delay, set variable DELAY to headerValue
         * - If parsing response body, i.e. PARSE_BODY === true. Concatenate every line till last line to a responseBody variable
         * - If on last line of response, do following:
         *   - Trim and remove whitespaces from the responseBody
         *   - Compile the Handlebars to generate a final response
         *   - Set PARSE_BODY flag back to false and responseBody to blank
         *   - Set express.Response Status code to response.status
         *   - Send the generated Response, from a timeout set to send the response after a DELAY value
         * @param {string} mockFile location of of the closest mached mockfile for incoming request
         */
        this.prepareResponse = (mockFile) => {
            let PARSE_BODY = false;
            let responseBody = "";
            let response = {
                status: 404,
                body: '{"error": "Not Found"}',
                headers: {
                    "content-type": "application/json",
                },
            };
            const template = handlebars_1.default.compile(fs_1.default.readFileSync(mockFile).toString());
            let fileResponse = template({ request: this.req, logger: logger_1.default });
            if (fileResponse.includes("====")) {
                const fileContentArray = removeBlanks(fileResponse.split("===="));
                fileResponse = fileContentArray[Math.floor(Math.random() * fileContentArray.length)];
            }
            const fileContent = fileResponse.trim().split(os_1.default.EOL);
            fileContent.forEach((line, index) => {
                if (line === "") {
                    PARSE_BODY = true;
                }
                if (line.includes("HTTP")) {
                    const regex = /(?<=HTTP\/\d).*?\s+(\d{3,3})/i;
                    if (!regex.test(line)) {
                        logger_1.default.error("Response code should be valid string");
                        throw new Error("Response code should be valid string");
                    }
                    response.status = line.match(regex)[1];
                    logger_1.default.debug("Response Status set to " + response.status);
                }
                else {
                    if (line !== "" && !PARSE_BODY) {
                        let headerKey = line.split(":")[0];
                        let headerValue = line.split(":")[1];
                        if (headerKey === "Response-Delay") {
                            DELAY = headerValue;
                            logger_1.default.debug(`Delay Set ${headerValue}`);
                        }
                        else {
                            this.res.setHeader(headerKey, headerValue);
                            logger_1.default.debug(`Headers Set ${headerKey}: ${headerValue}`);
                        }
                    }
                }
                if (PARSE_BODY) {
                    responseBody = responseBody + line;
                }
                if (index == fileContent.length - 1) {
                    this.res.statusCode = response.status;
                    if (responseBody.includes("camouflage_file_helper")) {
                        const fileResponse = responseBody.split(";")[1];
                        setTimeout(() => {
                            this.res.sendFile(fileResponse);
                        }, DELAY);
                    }
                    else {
                        responseBody = responseBody.replace(/\s+/g, " ").trim();
                        responseBody = responseBody.replace(/{{{/, "{ {{");
                        responseBody = responseBody.replace(/}}}/, "}} }");
                        const template = handlebars_1.default.compile(responseBody);
                        try {
                            const codeResponse = JSON.parse(responseBody);
                            switch (codeResponse["CamouflageResponseType"]) {
                                case "code":
                                    this.res.statusCode = codeResponse["status"] || this.res.statusCode;
                                    if (codeResponse["headers"]) {
                                        Object.keys(codeResponse["headers"]).forEach((header) => {
                                            this.res.setHeader(header, codeResponse["headers"][header]);
                                        });
                                    }
                                    setTimeout(() => {
                                        logger_1.default.debug(`Generated Response ${codeResponse["body"]}`);
                                        this.res.send(codeResponse["body"]);
                                    });
                                    break;
                                default:
                                    setTimeout(() => {
                                        logger_1.default.debug(`Generated Response ${template({ request: this.req, logger: logger_1.default })}`);
                                        this.res.send(template({ request: this.req, logger: logger_1.default }));
                                    }, DELAY);
                                    break;
                            }
                        }
                        catch (error) {
                            logger_1.default.warn(error.message);
                            setTimeout(() => {
                                logger_1.default.debug(`Generated Response ${template({ request: this.req, logger: logger_1.default })}`);
                                this.res.send(template({ request: this.req, logger: logger_1.default }));
                            }, DELAY);
                        }
                    }
                    PARSE_BODY = false;
                    responseBody = "";
                    DELAY = 0;
                }
            });
        };
        this.req = req;
        this.mockDir = mockDir;
        this.res = res;
    }
}
exports.HttpParser = HttpParser;
const removeBlanks = (array) => {
    return array.filter(function (i) {
        return i;
    });
};
const getWildcardPath = (dir, mockDir) => {
    let steps = removeBlanks(dir.split("/"));
    let testPath;
    let newPath = path_1.default.resolve(mockDir);
    while (steps.length) {
        let next = steps.shift();
        testPath = path_1.default.join(newPath, next);
        if (fs_1.default.existsSync(testPath)) {
            newPath = testPath;
            testPath = path_1.default.join(newPath, next);
        }
        else {
            testPath = path_1.default.join(newPath, "__");
            if (fs_1.default.existsSync(testPath)) {
                newPath = testPath;
                continue;
            }
            else {
                newPath = testPath;
                break;
            }
        }
    }
    return newPath;
};
//# sourceMappingURL=HttpParser.js.map