// Connect common api's.
import CommonAPI from "../API/Index";
import { AppVersion, IsBillingEnabled, getFrontendEnvVars, } from "../EnvironmentConfig";
import LocalCache from "../Infrastructure/LocalCache";
import HttpMetricsMiddleware from "../Middleware/HttpMetricsMiddleware";
import "./Environment";
import Express, { ExpressJson, ExpressRaw, ExpressStatic, ExpressUrlEncoded, headerValueToString, } from "./Express";
import logger, { getLogAttributesFromRequest, } from "./Logger";
import "./Process";
import Response from "./Response";
import SpanUtil from "./Telemetry/SpanUtil";
import TelemetryContext from "./Telemetry/TelemetryContext";
import { api } from "@opentelemetry/sdk-node";
import StatusCode from "../../Types/API/StatusCode";
import HTTPErrorResponse from "../../Types/API/HTTPErrorResponse";
import Exception from "../../Types/Exception/Exception";
import NotFoundException from "../../Types/Exception/NotFoundException";
import ServerException from "../../Types/Exception/ServerException";
import JSONFunctions from "../../Types/JSONFunctions";
import Typeof from "../../Types/Typeof";
import CookieParser from "cookie-parser";
import cors from "cors";
import zlib from "zlib";
import crypto from "crypto";
import path from "path";
import "ejs";
// Make sure we have stack trace for debugging.
Error.stackTraceLimit = Infinity;
const app = Express.getExpressApp();
app.disable("x-powered-by");
app.set("port", process.env["PORT"]);
app.set("view engine", "ejs");
/*
 * Enable trust proxy to correctly interpret X-Forwarded-* headers from reverse proxies
 * This is needed for req.protocol, req.ip to be correct when behind nginx/load balancers
 */
app.set("trust proxy", true);
app.use(CookieParser());
const jsonBodyParserMiddleware = ExpressJson({
    limit: "50mb",
    extended: true,
    verify: (req, _res, buf) => {
        req.rawBody = buf.toString();
        logger.debug(`Raw JSON Body for signature verification captured`, getLogAttributesFromRequest(req));
    },
}); // 50 MB limit.
const urlEncodedMiddleware = ExpressUrlEncoded({
    limit: "50mb",
    extended: true,
    verify: (req, _res, buf) => {
        req.rawFormUrlEncodedBody = buf.toString();
        req.rawBody = buf.toString(); // Also set rawBody for consistency
        logger.debug(`Raw Form Url Encoded Body: ${req.rawFormUrlEncodedBody}`, getLogAttributesFromRequest(req));
    },
}); // 50 MB limit.
const setDefaultHeaders = (req, res, next) => {
    if (typeof req.body === Typeof.String) {
        req.body = JSONFunctions.parse(req.body);
    }
    res.header("Access-Control-Allow-Credentials", "true");
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS");
    res.header("Access-Control-Allow-Headers", "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Authorization, DNT, X-CustomHeader, Keep-Alive, User-Agent, If-Modified-Since, Cache-Control, Content-Type");
    next();
};
app.use(cors());
app.use(HttpMetricsMiddleware);
app.use(setDefaultHeaders);
// Set the view engine to ejs
app.set("view engine", "ejs");
/*
 * Add limit of 10 MB to avoid "Request Entity too large error"
 * https://stackoverflow.com/questions/19917401/error-request-entity-too-large
 */
// Handle SCIM content type before JSON middleware
app.use((req, _res, next) => {
    const contentType = req.headers["content-type"];
    if (contentType && contentType.includes("application/scim+json")) {
        // Set content type to application/json so express.json() can parse it
        req.headers["content-type"] = "application/json";
    }
    next();
});
/*
 * Parse protobuf (binary) bodies for non-OTLP routes.
 * OTLP HTTP ingestion bypasses the global body parsers and handles raw/gzip
 * payloads in the telemetry router to avoid conflicts with the merged app stack.
 */
const protobufBodyParserMiddleware = ExpressRaw({
    type: ["application/x-protobuf", "application/protobuf"],
    limit: "50mb",
});
app.use((req, res, next) => {
    if (req.path.includes("/otlp/v1/")) {
        return next();
    }
    const contentType = headerValueToString(req.headers["content-type"]);
    const contentEncoding = headerValueToString(req.headers["content-encoding"]);
    if (contentEncoding === null || contentEncoding === void 0 ? void 0 : contentEncoding.includes("gzip")) {
        const buffers = [];
        req.on("data", (chunk) => {
            buffers.push(chunk);
        });
        req.on("end", () => {
            const buffer = Buffer.concat(buffers);
            zlib.gunzip(buffer, (err, decoded) => {
                if (err) {
                    logger.error(err, getLogAttributesFromRequest(req));
                    return Response.sendErrorResponse(req, res, new ServerException("Error decompressing data"));
                }
                req.body = decoded;
                next();
            });
        });
    }
    else if (contentType &&
        (contentType.includes("application/x-protobuf") ||
            contentType.includes("application/protobuf"))) {
        protobufBodyParserMiddleware(req, res, next);
    }
    else {
        jsonBodyParserMiddleware(req, res, next);
    }
});
app.use((req, res, next) => {
    var _a;
    if (req.path.includes("/otlp/v1/") ||
        ((_a = headerValueToString(req.headers["content-encoding"])) === null || _a === void 0 ? void 0 : _a.includes("gzip"))) {
        next();
    }
    else {
        urlEncodedMiddleware(req, res, next);
    }
});
app.use((_req, _res, next) => {
    // set span status code to OK by default. If the error occurs, it will be updated in the error handler.
    const span = api.trace.getSpan(api.context.active());
    if (span) {
        span.setStatus({ code: api.SpanStatusCode.OK });
    }
    next();
});
app.use((req, _res, next) => {
    const requestId = crypto.randomUUID();
    req.requestId = requestId;
    /*
     * Open a telemetry-context scope for the entire request. requestId is seeded
     * here; projectId/userId are added later by the auth middleware. Because
     * ContextSpanProcessor and Logger read this ambient context, every span and
     * log produced downstream inherits it automatically.
     */
    TelemetryContext.runWithContext({ requestId: requestId }, () => {
        SpanUtil.addAttributesToCurrentSpan({
            requestId: requestId,
        });
        next();
    });
});
const init = async (data) => {
    const { appName, port, isFrontendApp = false } = data;
    logger.info(`App Version: ${AppVersion.toString()}`);
    await Express.launchApplication(appName, port);
    LocalCache.setString("app", "name", appName);
    CommonAPI({
        appName,
        statusOptions: data.statusOptions,
    });
    if (isFrontendApp) {
        app.use(ExpressStatic("/usr/src/app/public"));
        app.get([`/${appName}/env.js`, "/env.js"], async (req, res, next) => {
            try {
                // ping api server for database config.
                const env = getFrontendEnvVars();
                const script = `
    if(!window.process){
      window.process = {}
    }

    if(!window.process.env){
      window.process.env = {}
    }
    window.process.env = ${JSON.stringify(env)};
  `;
                Response.sendJavaScriptResponse(req, res, script);
            }
            catch (err) {
                return next(err);
            }
        });
        app.use(`/${appName}`, ExpressStatic(path.resolve(process.cwd(), "public")));
        app.get(`/${appName}/dist/Index.js`, (_req, res) => {
            res.sendFile(path.resolve(process.cwd(), "public/dist/Index.js"));
        });
        /*
         * Return 404 for missing static assets instead of falling through to SPA catch-all.
         * Without this, missing JS/CSS chunks get served as HTML (index.ejs),
         * which causes "Failed to fetch dynamically imported module" errors.
         */
        app.get([`/${appName}/dist/*`, `/${appName}/assets/*`], (_req, res) => {
            res.status(404).send("Not found");
        });
        app.get(["/*", `/${appName}/*`], async (_req, res, next) => {
            try {
                const renderLogAttributes = getLogAttributesFromRequest(_req);
                logger.debug("Rendering index page", renderLogAttributes);
                let variables = {};
                if (data.getVariablesToRenderIndexPage) {
                    logger.debug("Getting variables to render index page", renderLogAttributes);
                    try {
                        const variablesToRenderIndexPage = await data.getVariablesToRenderIndexPage(_req, res);
                        variables = Object.assign(Object.assign({}, variables), variablesToRenderIndexPage);
                    }
                    catch (error) {
                        logger.error(error, renderLogAttributes);
                    }
                }
                logger.debug("Rendering index page with variables: ", renderLogAttributes);
                logger.debug(variables, renderLogAttributes);
                if (res.headersSent) {
                    logger.debug("Response already sent while preparing index page. Skipping render.", renderLogAttributes);
                    return;
                }
                return res.render(path.resolve(process.cwd(), "views/index.ejs"), Object.assign({ enableGoogleTagManager: IsBillingEnabled || false }, variables));
            }
            catch (err) {
                return next(err);
            }
        });
    }
    return app;
};
const addDefaultRoutes = async () => {
    app.post("*", (req, res) => {
        return Response.sendErrorResponse(req, res, new NotFoundException(`Page not found - ${req.url}`));
    });
    app.put("*", (req, res) => {
        return Response.sendErrorResponse(req, res, new NotFoundException(`Page not found - ${req.url}`));
    });
    app.delete("*", (req, res) => {
        return Response.sendErrorResponse(req, res, new NotFoundException(`Page not found - ${req.url}`));
    });
    app.get("*", (req, res) => {
        return Response.sendErrorResponse(req, res, new NotFoundException(`Page not found - ${req.url}`));
    });
    // Attach Error Handler.
    app.use((err, _req, res, next) => {
        var _a;
        logger.error(err, getLogAttributesFromRequest(_req));
        // Mark span as error.
        if (err) {
            const span = api.trace.getSpan(api.context.active());
            if (span) {
                // record exception
                span.recordException(err);
                // set span status code to ERROR
                span.setStatus({
                    code: api.SpanStatusCode.ERROR,
                    message: err.message,
                });
            }
        }
        if (res.headersSent) {
            return next(err);
        }
        if (err instanceof Promise) {
            err.catch((exception) => {
                if (StatusCode.isValidStatusCode(exception.code)) {
                    res.status(exception.code);
                    res.send({ error: exception.message });
                }
                else {
                    res.status(500);
                    res.send({ error: "Server Error" });
                }
            });
        }
        else if (err instanceof HTTPErrorResponse) {
            const errorStatusCode = StatusCode.isValidStatusCode(err.statusCode)
                ? err.statusCode
                : 500;
            const payload = (_a = err.jsonData) !== null && _a !== void 0 ? _a : {
                error: err.message || "Server Error",
            };
            res.status(errorStatusCode);
            res.send(payload);
        }
        else if (err instanceof Exception) {
            res.status(err.code);
            res.send({ error: err.message });
        }
        else {
            res.status(500);
            res.send({ error: "Server Error" });
        }
    });
};
export default { init, addDefaultRoutes };
//# sourceMappingURL=StartServer.js.map