// Tests for basic MCP protocol compliance
import assert from "node:assert";
import { ChildProcess, spawn } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { after, before, describe, it } from "node:test";
import { fileURLToPath } from "node:url";
import {
  createMockMCPRequest,
  expectedMCPTools,
  restoreConsole,
  suppressConsole,
  validateMCPResponse,
  validateToolSchema,
} from "./setup.ts";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

describe("Anrubic MCP Protocol Tests", () => {
  let serverProcess: ChildProcess;

  before(async () => {
    suppressConsole();
    // We'll test the actual compiled server
    const serverPath = path.join(__dirname, "../dist/index.js");

    // Ensure server is built
    assert.ok(fs.existsSync(serverPath), "Server must be built before testing");
  });

  after(() => {
    if (serverProcess) {
      serverProcess.kill();
    }
    restoreConsole();
  });

  async function sendMCPRequest(request: any): Promise<any> {
    return new Promise((resolve, reject) => {
      const serverPath = path.join(__dirname, "../dist/index.js");
      const process = spawn("node", [serverPath], {
        stdio: ["pipe", "pipe", "pipe"],
      });

      let output = "";
      let timeoutId: NodeJS.Timeout;

      const cleanup = () => {
        if (timeoutId) clearTimeout(timeoutId);
        process.kill();
      };

      // Set timeout for the request
      timeoutId = setTimeout(() => {
        cleanup();
        reject(new Error("Request timed out"));
      }, 10000); // 10 second timeout

      process.stdout.on("data", (data) => {
        output += data.toString();

        // Look for JSON-RPC response
        const lines = output.split("\n");
        for (const line of lines) {
          const trimmed = line.trim();
          if (trimmed.startsWith("{") && trimmed.includes('"jsonrpc"')) {
            try {
              const response = JSON.parse(trimmed);
              cleanup();
              resolve(response);
              return;
            } catch (e) {
              // Not valid JSON yet, continue
            }
          }
        }
      });

      process.stderr.on("data", (data) => {
        // Ignore stderr for now (server startup logs)
      });

      process.on("error", (error) => {
        cleanup();
        reject(error);
      });

      process.on("exit", (code) => {
        if (code !== 0) {
          cleanup();
          reject(new Error(`Process exited with code ${code}`));
        }
      });

      // Send the request
      process.stdin.write(JSON.stringify(request) + "\n");
      process.stdin.end();
    });
  }

  it("should respond to tools/list request", async () => {
    const request = createMockMCPRequest("tools/list");
    const response = await sendMCPRequest(request);

    validateMCPResponse(response, request.id);

    assert.ok(response.result.tools, "Response should contain tools array");
    assert.ok(Array.isArray(response.result.tools), "Tools should be an array");
    assert.ok(
      response.result.tools.length > 0,
      "Should have at least one tool"
    );
  });

  it("should have all expected MCP tools", async () => {
    const request = createMockMCPRequest("tools/list");
    const response = await sendMCPRequest(request);

    const tools = response.result.tools;
    const toolNames = tools.map((tool: any) => tool.name);

    for (const expectedTool of expectedMCPTools) {
      assert.ok(
        toolNames.includes(expectedTool),
        `Should include tool: ${expectedTool}`
      );
    }
  });

  it("should have valid tool schemas", async () => {
    const request = createMockMCPRequest("tools/list");
    const response = await sendMCPRequest(request);

    const tools = response.result.tools;

    for (const tool of tools) {
      assert.doesNotThrow(
        () => validateToolSchema(tool),
        `Tool ${tool.name} should have valid schema`
      );
    }
  });

  it("should respond with proper JSON-RPC format", async () => {
    const request = createMockMCPRequest("tools/list", undefined, 42);
    const response = await sendMCPRequest(request);

    assert.strictEqual(response.jsonrpc, "2.0", "Should use JSON-RPC 2.0");
    assert.strictEqual(response.id, 42, "Should echo request ID");
    assert.ok(response.result, "Should have result field");
    assert.ok(!response.error, "Should not have error field on success");
  });

  it("should handle invalid method gracefully", async () => {
    const request = createMockMCPRequest("invalid/method");

    try {
      const response = await sendMCPRequest(request);
      // If we get a response, it should be an error
      assert.ok(response.error, "Should return error for invalid method");
    } catch (error) {
      // It's also acceptable if the process exits/times out for invalid methods
      assert.ok(true, "Server may exit or timeout for invalid methods");
    }
  });
});
