import { Type } from "@sinclair/typebox";
const TestRequestBody = Type.Object({
    url: Type.String(),
    method: Type.String(),
    deployment: Type.Optional(Type.String()),
    headers: Type.Optional(Type.Record(Type.String(), Type.String())),
    body: Type.Optional(Type.String()),
});
/**
 * Emit a proxy-LEVEL failure exactly as the real test-proxy does: a non-2xx
 * problem+json body flagged with the `x-test-proxy-error` header carrying the
 * error code (never a 200-wrapped relay). Mirroring these locally means bad
 * requests surface identically in `zuplo dev` and in staging/prod instead of
 * being masked as upstream failures.
 */
function sendProxyError(reply, { status, code, title, detail, instance, }) {
    return reply
        .status(status)
        .header("x-test-proxy-error", code)
        .header("content-type", "application/problem+json")
        .send({
        type: `https://zuplo.com/problems/${code}`,
        title,
        status,
        code,
        detail,
        ...(instance ? { instance } : {}),
    });
}
/**
 * The relay target must be an absolute http(s) URL — the portal folds the
 * target host + path into `url` (buildTestProxyTargetUrl). A relative path or a
 * non-http protocol can't be relayed, so reject it up front rather than letting
 * `fetch` throw and mislabel it as an upstream failure.
 */
function isAbsoluteHttpUrl(value) {
    let parsed;
    try {
        parsed = new URL(value);
    }
    catch {
        return false;
    }
    return parsed.protocol === "http:" || parsed.protocol === "https:";
}
export async function createTestRoutes(fastify, options) {
    const { logger } = options;
    // Test endpoint that proxies requests to the local Zuplo gateway
    fastify.post("/", {
        schema: {
            body: TestRequestBody,
        },
    }, async (request, reply) => {
        const { url, method, deployment, headers = {}, body } = request.body;
        logger.info({ deployment }, `Proxying test request: ${method} ${url}`);
        if (!isAbsoluteHttpUrl(url)) {
            return sendProxyError(reply, {
                status: 400,
                code: "invalid_request",
                title: "Invalid Request",
                detail: `The request URL must be an absolute http(s) URL: ${url}`,
            });
        }
        const isGetOrHead = ["GET", "HEAD"].includes(method.toUpperCase());
        if (isGetOrHead && body) {
            return sendProxyError(reply, {
                status: 400,
                code: "get_or_head_with_body",
                title: "Invalid Request",
                detail: "GET and HEAD requests can't include a body.",
            });
        }
        // Clean headers - remove host and other problematic headers
        const cleanHeaders = {};
        for (const [key, value] of Object.entries(headers)) {
            const lowerKey = key.toLowerCase();
            // Skip headers that shouldn't be forwarded
            if (lowerKey !== "host" && lowerKey !== "connection") {
                cleanHeaders[key] = value;
            }
        }
        const fetchOptions = {
            method,
            headers: cleanHeaders,
        };
        if (body && !isGetOrHead) {
            fetchOptions.body = body;
        }
        // Make the request to the local gateway
        let response;
        try {
            response = await fetch(url, fetchOptions);
        }
        catch (err) {
            if (!(err instanceof TypeError)) {
                logger.error({ err, method, url }, "Error proxying test request");
            }
            else {
                logger.error({ method, url }, "Error proxying test request");
            }
            return sendProxyError(reply, {
                status: 502,
                code: "upstream_request_failed",
                title: "Local Gateway Unavailable",
                detail: "Could not connect to the local Zuplo gateway. Make sure your local gateway is running.",
                instance: url,
            });
        }
        // Capture response
        const responseBody = await response.text();
        const responseHeaders = {};
        // Convert headers to plain object
        response.headers.forEach((value, key) => {
            responseHeaders[key] = value;
        });
        // Return test result in the format expected by the frontend
        reply.status(200).send({
            status: response.status,
            statusText: response.statusText,
            headers: responseHeaders,
            body: responseBody,
        });
    });
}
//# sourceMappingURL=test.js.map