import { AUTH_PLACEHOLDER_TOKEN } from "../types/TestTypes.js";
import { isAuthorizationHeaderName } from "../utils/workspaceAuth.js";
import { inferExpectedStatus } from "../utils/httpDefaults.js";
import { logger } from "../utils/logger.js";
import { stageGeneratedPaths, resolveOutputDir } from "../utils/gitStaging.js";
import { getTestsRepoDir } from "../utils/AnalysisStateManager.js";
import fs from "fs";
import path from "path";
// Keys that trigger built-in prototype setters when used as bracket-notation
// property names on a plain object — guard against prototype pollution from
// LLM-controlled or user-controlled JSON input.
const PROTO_KEYS = new Set(["__proto__", "constructor", "prototype"]);
export class ScenarioGenerationService {
    async parseScenario(params) {
        try {
            logger.info("Parsing scenario into API requests", {
                scenarioName: params.scenarioName,
            });
            const traceRequest = this.generateTraceRequestFromInput(params);
            if (!traceRequest) {
                return {
                    content: [
                        {
                            type: "text",
                            text: "Could not generate a trace request from the provided scenario.",
                        },
                    ],
                    isError: true,
                };
            }
            // In cross-repo mode, redirect outputDir to the test repo clone.
            const resolved = resolveOutputDir(params.outputDir, getTestsRepoDir());
            if (resolved !== params.outputDir) {
                logger.info("Cross-repo: redirecting scenario outputDir to test repo", {
                    original: params.outputDir,
                    redirected: resolved,
                });
                params.outputDir = resolved;
            }
            const scenarioName = params.scenarioName.replace(/ /g, "-").toLowerCase();
            const fileName = `scenario_${scenarioName}.json`;
            const filePath = path.join(params.outputDir, fileName);
            try {
                let existingRequests = [];
                if (fs.existsSync(filePath)) {
                    try {
                        const existingContent = fs.readFileSync(filePath, "utf8");
                        existingRequests = JSON.parse(existingContent);
                        if (!Array.isArray(existingRequests)) {
                            existingRequests = [];
                        }
                    }
                    catch {
                        existingRequests = [];
                    }
                }
                if (existingRequests.length > 0) {
                    const lastTimestamp = existingRequests[existingRequests.length - 1].Timestamp;
                    const lastMs = new Date(lastTimestamp).getTime();
                    const newMs = Math.max(lastMs + 1000, Date.now());
                    traceRequest.Timestamp = new Date(newMs).toISOString();
                }
                existingRequests.push(traceRequest);
                fs.writeFileSync(filePath, JSON.stringify(existingRequests, null, 2), "utf8");
                // Stage so testbot includes the generated files in its output commit.
                await stageGeneratedPaths(filePath);
                logger.info("Trace request added to file", {
                    filePath,
                    totalRequests: existingRequests.length,
                });
            }
            catch (error) {
                const errorMessage = error instanceof Error ? error.message : String(error);
                return {
                    content: [
                        {
                            type: "text",
                            text: `Error writing trace file: ${errorMessage}`,
                        },
                    ],
                    isError: true,
                };
            }
            return {
                content: [
                    {
                        type: "text",
                        text: `**Trace Request Generated**

**Original Scenario:** "${params.scenarioName}"

**File Updated:** ${filePath}

**Generated Trace Request:**

\`\`\`json
${JSON.stringify(traceRequest, null, 2)}
\`\`\`

**File Details:**
- Output file: ${fileName}
- Full path: ${filePath}
- Action: Request added to trace file

**Next Steps:**
- Call this tool again with the next step in your scenario to add more requests
- Use the complete JSON file as input for integration or load test generation`,
                    },
                ],
                isError: false,
            };
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            return {
                content: [
                    {
                        type: "text",
                        text: `Error generating trace request: ${errorMessage}`,
                    },
                ],
                isError: true,
            };
        }
    }
    generateTraceRequestFromInput(params) {
        let destination = params.destination;
        let scheme = "https";
        let port = 443;
        let basePath = "";
        if (params.baseURL) {
            try {
                const parsed = new URL(params.baseURL);
                scheme = parsed.protocol.replace(":", "");
                destination = parsed.hostname;
                port = parsed.port
                    ? parseInt(parsed.port, 10)
                    : scheme === "https"
                        ? 443
                        : 80;
                basePath = parsed.pathname.replace(/\/$/, "");
            }
            catch {
                logger.warning("Could not parse baseURL, using destination param", {
                    baseURL: params.baseURL,
                });
            }
        }
        const timestamp = new Date().toISOString();
        const method = params.method;
        const statusCode = params.statusCode ?? inferExpectedStatus(method);
        const requestBody = params.requestBody ||
            (method === "GET" || method === "DELETE" ? "" : "{}");
        const responseHeaders = params.responseHeaders
            || { "Content-Type": ["application/json"] };
        const isJsonResponse = (responseHeaders["Content-Type"] || [])
            .some(v => v.includes("application/json"));
        const responseBody = params.responseBody || (isJsonResponse ? "{}" : "");
        const requestHeaders = {
            "Content-Type": ["application/json"],
        };
        // Go backend expects url.Values (map[string][]string). We always produce
        // single-element arrays — the Go codegen normalises multi-element string arrays
        // into a JSON array string (e.g. ["sale","new"]), which is not what most APIs
        // expect. Comma-joining covers the common case; APIs needing true repeated keys
        // (e.g. ?tags=sale&tags=new) are not supported by the current Go codegen path.
        const queryParams = {};
        if (params.queryParams) {
            try {
                const parsed = JSON.parse(params.queryParams);
                if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
                    for (const [k, v] of Object.entries(parsed)) {
                        if (PROTO_KEYS.has(k))
                            continue;
                        if (v === null)
                            continue;
                        let value;
                        if (Array.isArray(v)) {
                            const items = v
                                .filter((item) => item !== null)
                                .map((item) => (typeof item === "object" ? JSON.stringify(item) : String(item)));
                            if (items.length === 0)
                                continue;
                            value = items.join(",");
                        }
                        else if (typeof v === "object") {
                            logger.warning("Nested object in queryParams — JSON-stringifying as fallback; LLM should provide flat keys", { key: k });
                            value = JSON.stringify(v);
                        }
                        else {
                            value = String(v);
                        }
                        queryParams[k] = [value];
                    }
                }
                else {
                    logger.warning("queryParams JSON is not a plain object, ignoring", {
                        queryParams: params.queryParams,
                    });
                }
            }
            catch {
                logger.warning("Could not parse queryParams, ignoring", {
                    queryParams: params.queryParams,
                });
            }
        }
        const authHeaderName = params.authHeader ?? "";
        if (authHeaderName) {
            if (params.authToken) {
                requestHeaders[authHeaderName] = [params.authToken];
            }
            else if (isAuthorizationHeaderName(authHeaderName)) {
                const authPrefix = params.authScheme || "";
                requestHeaders[authHeaderName] = authPrefix
                    ? [`${authPrefix} ${AUTH_PLACEHOLDER_TOKEN}`]
                    : [AUTH_PLACEHOLDER_TOKEN];
            }
            else {
                requestHeaders[authHeaderName] = [AUTH_PLACEHOLDER_TOKEN];
            }
        }
        return {
            Source: "192.168.65.1:39998",
            Destination: destination,
            RequestBody: requestBody,
            ResponseBody: responseBody,
            RequestHeaders: requestHeaders,
            ResponseHeaders: responseHeaders,
            Method: method,
            Path: basePath ? basePath + params.path : params.path,
            QueryParams: queryParams,
            StatusCode: statusCode,
            Port: port,
            Timestamp: timestamp,
            Scheme: scheme,
        };
    }
}
