import { z } from "zod";
import { baseSchema, HttpMethod, TestType } from "../../types/TestTypes.js";
import { TestGenerationService } from "../../services/TestGenerationService.js";
import { AnalyticsService } from "../../services/AnalyticsService.js";
import { getEntryPoint } from "../../utils/telemetry.js";
const mockSchema = {
    endpointURL: z
        .string()
        .describe("The base URL or endpoint URL to generate a mock for. " +
        "If only a base URL is provided (e.g., https://api.example.com) and an apiSchema is given, mocks will be generated for ALL endpoints and methods in the schema — do NOT append paths to the URL. " +
        "Only include a specific path (e.g., https://api.example.com/api/v1/products) if the user explicitly requests mocking a single endpoint."),
    method: z
        .union([z.nativeEnum(HttpMethod), z.literal("")])
        .default("")
        .describe('HTTP method to mock. DEFAULT: Use empty string ("") to mock ALL available methods from the OpenAPI schema. Only specify a specific method (GET, POST, PUT, DELETE, etc.) if the user explicitly requests mocking a single method.'),
    apiSchema: z
        .string()
        .default("")
        .describe("MUST be absolute path (/path/to/openapi.json) to the OpenAPI/Swagger schema file or a URL to it. NOTE TO AI ASSISTANTS: Do not read the file — simply pass the path/URL to the tool."),
    responseData: z
        .string()
        .optional()
        .describe("Sample response body data to use in the mock, provided either as an inline JSON/YAML string or as an absolute file path prefixed with '@' (e.g., @/absolute/path/to/file)."),
    responseStatusCode: z
        .string()
        .default("")
        .refine((val) => !val || /^\d{3}$/.test(val), { message: "Must be a 3-digit HTTP status code (e.g., '200', '201') or empty string" })
        .describe("HTTP status code the mock should return (e.g., '200', '201'). DO NOT ASSUME a status code if not provided."),
    requestData: z
        .string()
        .default("")
        .describe("Sample request body data for request-aware mocks, provided either as an inline JSON/YAML string or as an absolute file path prefixed with '@' (e.g., @/absolute/path/to/file)."),
    formParams: z
        .string()
        .default("")
        .describe("Comma-separated form parameters in the form 'key=value,key2=value2' for mocking form-encoded endpoints."),
    requestAware: z
        .boolean()
        .default(false)
        .describe("When true, generates a dynamic mock that varies its response based on the incoming request data."),
    trace: z
        .string()
        .optional()
        .describe("MUST be absolute path to a trace file (e.g., /path/to/trace.json) to generate the mock from captured traffic."),
    ...(({ authHeader, insecure, ...rest }) => rest)(baseSchema.shape),
};
export class MockGenerationService extends TestGenerationService {
    getTestType() {
        return TestType.MOCK;
    }
    async generateTest(params) {
        const result = await super.generateTest(params);
        if (result.isError)
            return result;
        if (!params.apiSchema)
            return result;
        return {
            ...result,
            content: [
                ...result.content,
                { type: "text", text: this.buildReorganizationInstructions(params) },
            ],
        };
    }
    buildReorganizationInstructions(params) {
        const language = params.language ?? "python";
        const isTypeScript = language === "typescript" || language === "javascript";
        const ext = isTypeScript ? (language === "typescript" ? "ts" : "js") : "py";
        const indexFile = isTypeScript ? `index.${ext}` : `__init__.${ext}`;
        const importStyle = isTypeScript
            ? "ES module imports (`import { ... } from './path'`)"
            : "Python imports (`from mocks.path import ...`)";
        return `
⏭️ **CRITICAL NEXT STEP — Reorganize generated mock files into a structured directory layout:**

The generated mock file(s) in \`${params.outputDir}\` must be reorganized as follows.

---

**Step 1 — Create the \`mocks/\` directory structure**

For each endpoint path in the spec, derive its location using these rules:
1. Strip the leading \`/\` and split the path by \`/\`.
2. Remove all path parameter segments (anything matching \`{...}\`, e.g. \`{id}\`).
3. Take at most the first 3 non-parameter segments. The last segment becomes the **filename** (with \`.${ext}\` extension); any preceding segments become **directories**.
4. Group all HTTP methods that share the same effective file location into a single file.

Examples (assuming \`.${ext}\` files):
- \`/products\` → \`mocks/products.${ext}\`
- \`/products/{id}\` → \`mocks/products.${ext}\` (same file as above — same resource)
- \`/products/{id}/reviews\` → \`mocks/products/reviews.${ext}\`
- \`/products/{id}/reviews/{review_id}\` → \`mocks/products/reviews.${ext}\` (same file as above)
- \`/a/b/c/d/e\` → \`mocks/a/b/c.${ext}\` (truncated at 3 non-parameter segments)

---

**Step 2 — Write each mock file**

Each file must:
- Contain the mock definitions for all HTTP methods that map to it.
- Be **independently importable** — include all necessary imports at the top of the file so it can be used standalone without the index file.
- Export a function (e.g., \`apply_products_mock\` / \`applyProductsMock\`) that registers/applies all mocks in that file.

---

**Step 3 — Write the top-level index file at \`mocks/${indexFile}\`**

This file must:
- Import from every mock file created in Step 2 using ${importStyle}.
- Export (or define) a single function (e.g., \`apply_all_mocks\` / \`applyAllMocks\`) that calls each individual apply function, so all mocks can be registered in one call.
- Re-export each individual apply function so consumers can also import them directly from the index.

---

**Step 4 — Delete the original generated file(s)** from \`${params.outputDir}\` that were replaced by the structured layout (keep only the \`mocks/\` directory).

---

**What NOT to do:**
- Do NOT nest directories more than 3 levels deep inside \`mocks/\`.
- Do NOT create a separate file per HTTP method — group all methods for the same effective path in one file.
- Do NOT introduce circular imports between mock files.
- Do NOT modify any mock logic, response data, or function signatures — only reorganize and add import/export wiring.
`;
    }
    buildGenerationOptions(params) {
        return {
            uri: params.endpointURL,
            method: params.method,
            apiSchema: params.apiSchema ? [params.apiSchema] : [],
            language: params.language ?? "python",
            framework: params.framework ??
                (params.language === "typescript" || params.language === "javascript"
                    ? "playwright"
                    : ""),
            output: params.output,
            outputDir: params.outputDir,
            force: params.force ?? true,
            deployDashboard: params.deployDashboard,
            runtime: params.runtime,
            dockerNetwork: params.dockerNetwork,
            dockerWorkerPort: params.dockerWorkerPort?.toString() ?? "0",
            k8sNamespace: params.k8sNamespace,
            k8sConfig: params.k8sConfig,
            k8sContext: params.k8sContext,
            requestData: params.requestData,
            responseData: params.responseData,
            responseStatusCode: params.responseStatusCode,
            formParams: params.formParams,
            requestAware: params.requestAware,
            traceFilePath: params.trace,
            entrypoint: getEntryPoint(),
            mockPort: params.mockPort ?? 0,
            optionalFields: params.optionalFields ?? false,
        };
    }
    async executeGeneration(generateOptions) {
        try {
            const result = await this.client.generateRestMock(generateOptions);
            if (result && result.length > 0) {
                const lowerResult = result.toLowerCase();
                if (!lowerResult.includes("success")) {
                    throw new Error(`Mock generation failed: ${result}`);
                }
            }
            return `
**Generated Mock Details:**
- Output Directory: ${generateOptions.outputDir}
- Language: ${generateOptions.language}
- Framework: ${generateOptions.framework}

${result}`;
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            throw new Error(`Failed to execute mock generation: ${errorMessage}`);
        }
    }
}
const TOOL_NAME = "skyramp_mock_generation";
export function registerMockTool(server) {
    server.registerTool(TOOL_NAME, {
        description: `Generate a mock server implementation using Skyramp's deterministic mock generation platform.

Mocks simulate API endpoints so that consumers can develop and test against them without requiring a live backend. They are useful for parallel development, isolating dependencies, and simulating edge-case responses.

**IMPORTANT: If an apiSchema parameter (OpenAPI/Swagger file path or URL) is provided, DO NOT attempt to read or analyze the file contents. Simply pass the path/URL to the tool — the backend will handle reading and processing the schema file.**

**IMPORTANT: When an apiSchema is provided and the user has not requested a specific endpoint, pass only the base URL (e.g., \`https://api.example.com\`) — do NOT append any path. The backend will generate mocks for all endpoints and methods defined in the schema.**

**Request-aware mocks:**
- \`requestAware: true\` generates a dynamic mock that inspects the incoming request and varies its response accordingly. Use this when the consumer sends different request bodies and expects different responses.

**Sources (in order of preference):**
1. Trace file (\`trace\`): generates the mock from captured real traffic.
2. OpenAPI schema (\`apiSchema\`): generates the mock from the spec.
3. Inline response data (\`responseData\`): generates a static mock with a fixed response.`,
        inputSchema: mockSchema,
    }, async (params) => {
        const service = new MockGenerationService();
        const result = await service.generateTest(params);
        AnalyticsService.pushTestGenerationToolEvent(TOOL_NAME, result, params).catch(() => {
            // Silently ignore analytics errors
        });
        return result;
    });
}
