#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { registerStartTraceCollectionPrompt } from "./prompts/startTraceCollectionPrompts.js";
import { registerTraceTool } from "./tools/trace/startTraceCollectionTool.js";
import { registerTraceStopTool } from "./tools/trace/stopTraceCollectionTool.js";
import { registerExecuteSkyrampTestTool } from "./tools/executeSkyrampTestTool.js";
import { AUTH_PLACEHOLDER_TOKEN } from "./types/TestTypes.js";
import { AUTH_CONFLICT_ERROR_MSG } from "./prompts/test-recommendation/recommendationSections.js";
import { logger } from "./utils/logger.js";
import { registerUITestTool } from "./tools/generate-tests/generateUIRestTool.js";
import { registerSmokeTestTool } from "./tools/generate-tests/generateSmokeRestTool.js";
import { registerFuzzTestTool } from "./tools/generate-tests/generateFuzzRestTool.js";
import { registerContractTestTool } from "./tools/generate-tests/generateContractRestTool.js";
import { registerLoadTestTool } from "./tools/generate-tests/generateLoadRestTool.js";
import { registerIntegrationTestTool } from "./tools/generate-tests/generateIntegrationRestTool.js";
import { registerE2ETestTool } from "./tools/generate-tests/generateE2ERestTool.js";
import { registerLoginTool } from "./tools/auth/loginTool.js";
import { registerLogoutTool } from "./tools/auth/logoutTool.js";
import { registerFixErrorTool } from "./tools/fixErrorTool.js";
import { registerRecommendTestsPrompt } from "./prompts/test-recommendation/registerRecommendTestsPrompt.js";
import { registerModularizationTool } from "./tools/code-refactor/modularizationTool.js";
import { registerCodeReuseTool } from "./tools/code-refactor/codeReuseTool.js";
import { registerEnhanceAssertionsTool } from "./tools/code-refactor/enhanceAssertionsTool.js";
import { registerBatchScenarioTestTool } from "./tools/generate-tests/generateBatchScenarioRestTool.js";
import { registerMockTool } from "./tools/generate-tests/generateMockRestTool.js";
import { registerAnalyzeChangesTool, registerUiAnalyzeChangesTool, registerAnalyzeTestHealthTool, registerActionsTool, } from "./tools/test-management/index.js";
import { registerTestbotPrompt } from "./prompts/testbot/testbot-prompts.js";
import { registerTestbotResource } from "./resources/testbotResource.js";
import { registerSubmitReportTool } from "./tools/submitReportTool.js";
import { registerInitializeWorkspaceTool } from "./tools/workspace/initializeWorkspaceTool.js";
import { registerInitScanWorkspaceTool } from "./tools/workspace/initScanWorkspaceTool.js";
import { registerOneClickTool } from "./tools/one-click/oneClickTool.js";
import { registerAnalysisResources } from "./resources/analysisResources.js";
import { registerProgressResource } from "./resources/progressResource.js";
import { AnalyticsService } from "./services/AnalyticsService.js";
import { registerInitTriggerOnMCPInitialized } from "./utils/initAgent.js";
import { isTestbotEnabled } from "./utils/featureFlags.js";
import { registerPlaywrightTools, registerTraceRecordingPrompt, getPlaywrightTraceService, } from "./playwright/index.js";
const oneClickEnabled = process.env.SKYRAMP_FEATURE_ONE_CLICK === "1";
const oneClickInstructions = oneClickEnabled
    ? `\n- When the user asks to comprehensively, thoroughly, or deeply test a specific endpoint: MUST call \`skyramp_one_click_tool\` with workflow \`test_given_endpoint_comprehensively\` first. Do NOT self-orchestrate the steps manually.\n- When the user asks to scan the full repo, recommend, generate, and execute top N tests: MUST call \`skyramp_one_click_tool\` with workflow \`full_repo_scan_recommend_generate_and_execute_top_n_tests\`.`
    : "";
const server = new McpServer({
    name: "Skyramp MCP Server",
    version: "1.0.0",
}, {
    capabilities: {
        tools: {
            listChanged: true,
        },
        prompts: {
            listChanged: true,
        },
        resources: {
            listChanged: true,
        },
    },
    instructions: `Skyramp MCP Server — generates and executes API tests (fuzz, contract, integration, E2E, UI).

## Workspace Initialization (REQUIRED before the first Skyramp tool call)
If the workspace root is a git repo AND \`.skyramp/workspace.yml\` does not exist:
1. Call \`skyramp_init_scan\` with \`workspacePath\` → follow the returned instructions to discover all services.
2. Call \`skyramp_init_workspace\` with \`workspacePath\`, \`services\`, and the \`scanToken\` from step 1.
3. Proceed with the originally requested tool.
Skip only if: not a git repo, \`.skyramp/workspace.yml\` already exists, or user explicitly declines.
  
## Rules
- NEVER show CLI commands. NEVER attempt to install or configure the Skyramp CLI. ALWAYS use the MCP tools provided.
- For UI and E2E tests, there are TWO recording modes:
  1. **AI-driven recording** (default): Use the browser_* tools (browser_navigate, browser_click, etc.) to record interactions, then call skyramp_export_zip to export the trace, then call skyramp_ui_test_generation with the zip path.
  2. **Manual recording**: ONLY when the user explicitly says "manual recording", "record myself", "I will interact", or "Docker trace" — use skyramp_start_trace_collection / skyramp_stop_trace_collection to let the user interact with the browser themselves.
- To capture an authentication session for re-use (e.g. "save session", "store login", "record auth state"): call \`skyramp_start_trace_collection\` (\`playwrightSaveStoragePath\` defaults to saving the session in \`outputDir\` — no need to set it). Tell the user to log in once and then call \`skyramp_stop_trace_collection\` IMMEDIATELY after login completes. The saved file can be passed back as \`playwrightStoragePath\` on later trace runs, or referenced as \`storageState\` in generated tests.${oneClickInstructions}

## Test Management Flow
Use \`skyramp_analyze_changes\` as the single entry point for both test recommendations and test health analysis.

### Recommendations (2-step)
1. Call \`skyramp_analyze_changes\` with \`repositoryPath\` and \`scope\` → scans endpoints, computes branch diff, discovers existing tests → returns a \`stateFile\` **and** inline ranked test recommendations.
2. (Optional) Use the \`skyramp_recommend_tests\` prompt with \`stateFile\` in Cursor/IDE chat for a focused recommendation view.

### Health Analysis (4-step)
1. Call \`skyramp_analyze_changes\` with \`repositoryPath\` and \`scope\` → returns a \`stateFile\`.
2. Call \`skyramp_analyze_test_health\` with \`stateFile\` → runs drift analysis + health scoring + LLM semantic assessment.
3. (Optional) Execute tests using \`skyramp_execute_test\` with \`stateFile\` param → validates test status live and writes results back to stateFile for health scoring.
4. Call \`skyramp_actions\` with \`stateFile\` → executes UPDATE/REGENERATE/ADD recommendations (with execution-aware prioritization if step 3 ran).

After \`skyramp_analyze_changes\`, inspect enriched data via MCP Resources (use the \`sessionId\` returned in the output):
- \`skyramp://analysis/{sessionId}/summary\` — high-level overview
- \`skyramp://analysis/{sessionId}/endpoints\` — compact endpoint listing
- \`skyramp://analysis/{sessionId}/endpoints/{path}\` — full path detail
- \`skyramp://analysis/{sessionId}/endpoints/{path}/{method}\` — single method detail
- \`skyramp://analysis/{sessionId}/scenarios\` — drafted scenarios
- \`skyramp://analysis/{sessionId}/diff\` — branch diff context

## Workspace Defaults for Test Generation (MANDATORY)
Before calling ANY test generation tool, you MUST follow this flow:

1. **Read** the .skyramp/workspace.yml file to get the configured defaults.
2. **Extract** the \`language\`, \`framework\`, \`testDirectory\`, \`api.baseUrl\`, \`api.authHeader\`, and \`api.authType\` from the matching service in the services section.
3. **Use those values** as defaults for the test generation tool call. Pass the service \`testDirectory\` as the generation tool \`outputDir\`. Do NOT ask the user for these values if they are already configured in the workspace file.
4. **CRITICAL — endpointURL**: The \`endpointURL\` parameter MUST be the full URL to the specific endpoint being tested, NOT just the base URL. Construct it by combining \`api.baseUrl\` with the endpoint path. Example: if \`api.baseUrl\` is \`http://localhost:8000\` and the endpoint is \`/api/v1/products\`, pass \`endpointURL: "http://localhost:8000/api/v1/products"\`. NEVER pass just the base URL (e.g. \`http://localhost:8000\`) as \`endpointURL\`.
5. **CRITICAL — scenario generation**: When calling \`skyramp_batch_scenario_test_generation\`, ALWAYS pass:
   - \`baseURL\`: The full base URL from \`api.baseUrl\` (e.g., \`http://localhost:3000\`). This determines the scheme, host, and port in the generated trace. Without it, the trace defaults to https:443 which is almost always wrong for local development.
   - \`authHeader\`: Which HTTP header carries the auth credential. Get it from \`api.authHeader\` in workspace config. Examples: \`Authorization\` (Bearer/Token auth), \`X-Api-Key\` (API key auth), \`Cookie\` (session/cookie auth like NextAuth). Pass \`""\` to skip auth entirely (unauthenticated endpoints or \`api.authType: "none"\`).
   - \`authScheme\`: Only when \`authHeader\` is \`Authorization\`. The prefix before the token (e.g., \`"Bearer"\` → \`Authorization: Bearer <token>\`). **Derive from**: (1) OpenAPI spec \`securitySchemes\`/\`securityDefinitions\`, (2) source code auth middleware, (3) workspace \`api.authType\`. **Do NOT guess.**
   - \`authToken\`: The full header value, used verbatim. When omitted, \`SKYRAMP_PLACEHOLDER_TOKEN\` is auto-generated. Only provide when the header needs a specific format (e.g., \`"session=${AUTH_PLACEHOLDER_TOKEN}"\` for Cookie). **Do NOT fabricate token values.**
   - \`apiSchema\` is OPTIONAL — omit it for code-first apps without OpenAPI specs.
6. **CRITICAL — integration test from scenario**: When calling \`skyramp_integration_test_generation\` with a \`scenarioFile\`:
   - If workspace has \`api.authType\` set: omit auth params entirely — passing auth here alongside workspace \`authType\` causes "${AUTH_CONFLICT_ERROR_MSG}".
   - If workspace has no \`api.authType\`: pass \`authHeader\` only (no \`authScheme\`).
7. **If the workspace file does not exist**, or the needed values (language, framework, testDirectory) are missing from the workspace config, ASK the user which language, framework, and outputDir they want before calling the tool.
8. The user can always override workspace defaults by explicitly specifying values in their request.
`,
});
// Register prompts
logger.info("Starting prompt registration process");
const prompts = [
    registerStartTraceCollectionPrompt,
    registerRecommendTestsPrompt,
    registerTraceRecordingPrompt,
];
if (isTestbotEnabled()) {
    prompts.push(registerTestbotPrompt);
    registerTestbotResource(server);
    logger.info("TestBot prompt enabled via SKYRAMP_FEATURE_TESTBOT");
}
prompts.forEach((registerPrompt) => registerPrompt(server));
logger.info("All prompts registered successfully");
// Register test generation tools
const testGenerationTools = [
    registerSmokeTestTool,
    registerFuzzTestTool,
    registerContractTestTool,
    registerLoadTestTool,
    registerIntegrationTestTool,
    registerE2ETestTool,
    registerUITestTool,
    registerBatchScenarioTestTool,
    registerMockTool,
];
testGenerationTools.forEach((registerTool) => registerTool(server));
// Register modularization and code quality tools
const codeQualityTools = [
    registerModularizationTool,
    registerFixErrorTool,
    registerCodeReuseTool,
    registerEnhanceAssertionsTool,
];
codeQualityTools.forEach((registerTool) => registerTool(server));
// Register analysis resources (MCP Resources for enriched data access)
registerAnalysisResources(server);
registerProgressResource(server);
// Register unified test-management tools (replaces separate test-maintenance tools)
registerUiAnalyzeChangesTool(server);
registerAnalyzeChangesTool(server);
registerAnalyzeTestHealthTool(server);
registerActionsTool(server);
// Register workspace management tools
registerInitScanWorkspaceTool(server);
registerInitializeWorkspaceTool(server);
// Register one-click orchestrated workflows
if (oneClickEnabled) {
    registerOneClickTool(server);
    logger.info("One-click tools enabled via SKYRAMP_FEATURE_ONE_CLICK");
}
// Register other Skyramp tools
const infrastructureTools = [
    registerLoginTool,
    registerLogoutTool,
    registerExecuteSkyrampTestTool,
    registerTraceTool,
    registerTraceStopTool,
];
if (isTestbotEnabled()) {
    infrastructureTools.push(registerSubmitReportTool);
    logger.info("TestBot tools enabled via SKYRAMP_FEATURE_TESTBOT");
}
infrastructureTools.forEach((registerTool) => registerTool(server));
// Register Playwright browser tools (trace recording via browser automation)
registerPlaywrightTools(server).catch((err) => {
    logger.error("Failed to register Playwright tools", { error: err });
});
// Global error handlers for crash telemetry
process.on("uncaughtException", async (error) => {
    // Don't send analytics for EPIPE errors
    const isEpipeError = "code" in error && error.code === "EPIPE";
    if (isEpipeError) {
        process.exit(0);
    }
    logger.critical("Uncaught exception - MCP server crashing", {
        error: error.message,
        stack: error.stack,
    });
    try {
        await AnalyticsService.pushServerCrashEvent("uncaughtException", error.message, error.stack);
    }
    catch (telemetryError) {
        logger.error("Failed to send crash telemetry", { telemetryError });
    }
    process.exit(1);
});
// Start MCP server
async function main() {
    const transport = new StdioServerTransport();
    server.server.oninitialized = () => {
        registerInitTriggerOnMCPInitialized().catch((err) => {
            logger.error("Failed to run MCP initialized trigger", { error: err });
        });
    };
    await server.connect(transport);
    logger.info("MCP Server started successfully");
    // Listen for stdin closure (parent process disconnected)
    // Using multiple events for robustness across different disconnection scenarios
    let isShuttingDown = false;
    const handleParentDisconnect = (event) => {
        if (isShuttingDown)
            return;
        isShuttingDown = true;
        logger.info(`Parent process disconnected (${event}). Cleaning up and exiting...`);
        // Shut down Playwright browser before exiting
        const playwrightService = getPlaywrightTraceService();
        const cleanupPlaywright = playwrightService
            ? playwrightService.shutdown().catch(() => { })
            : Promise.resolve();
        cleanupPlaywright.finally(() => {
            server.close().finally(() => {
                process.exit(0);
            });
        });
    };
    process.stdin.on("end", () => handleParentDisconnect("stdin end"));
    process.stdin.on("close", () => handleParentDisconnect("stdin close"));
    process.stdin.on("error", (err) => {
        logger.error("STDIN error, parent likely disconnected", { error: err });
        handleParentDisconnect("stdin error");
    });
    // Handle process termination signals
    process.on("SIGTERM", () => handleParentDisconnect("SIGTERM"));
    process.on("SIGINT", () => handleParentDisconnect("SIGINT"));
}
main().catch(async (error) => {
    // Don't send analytics for EPIPE errors - broken pipe means we can't write anyway
    // Node.js system errors have a 'code' property with error codes like 'EPIPE'
    const isEpipeError = "code" in error && error.code === "EPIPE";
    if (isEpipeError) {
        process.exit(0);
    }
    logger.critical("Fatal error in main()", {
        error: error.message,
        stack: error.stack,
    });
    try {
        await AnalyticsService.pushServerCrashEvent("mainFatalError", error.message, error.stack);
    }
    catch (telemetryError) {
        logger.error("Failed to send crash telemetry", { telemetryError });
    }
    process.exit(1);
});
