#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { exec } from "child_process";
import { promisify } from "util";
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname, resolve } from "path";
import { fileURLToPath } from "url";
import { randomBytes } from "crypto";
import http from "http";
import https from "https";

const execAsync = promisify(exec);

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dirname, "..");

// Redirect console.log to stderr so it never corrupts the MCP stdio stream.
console.log = (...args) =>
  process.stderr.write(
    args
      .map((a) => (typeof a === "object" ? JSON.stringify(a) : String(a)))
      .join(" ") + "\n"
  );

// ---------------------------------------------------------------------------
// MCP Server
// ---------------------------------------------------------------------------

const server = new Server(
  { name: "teletraan-installer", version: "2.0.0" },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "resolve_claude_path",
      description: "Find and return the absolute path to the claude binary.",
      inputSchema: { type: "object", properties: {} },
    },
    {
      name: "create_directory",
      description: "Create a directory and all parent directories (mkdir -p).",
      inputSchema: {
        type: "object",
        properties: {
          path: { type: "string", description: "Absolute path to create" },
        },
        required: ["path"],
      },
    },
    {
      name: "copy_directory",
      description: "Recursively copy a directory from src to dest.",
      inputSchema: {
        type: "object",
        properties: {
          src: { type: "string", description: "Source directory path" },
          dest: { type: "string", description: "Destination directory path" },
        },
        required: ["src", "dest"],
      },
    },
    {
      name: "run_npm_install",
      description: "Run npm install in the given directory.",
      inputSchema: {
        type: "object",
        properties: {
          dir: { type: "string", description: "Directory to run npm install in" },
        },
        required: ["dir"],
      },
    },
    {
      name: "run_npm_build",
      description: "Run npm run build in the given directory.",
      inputSchema: {
        type: "object",
        properties: {
          dir: { type: "string", description: "Directory to run npm run build in" },
        },
        required: ["dir"],
      },
    },
    {
      name: "write_file",
      description:
        "Write content to a file, creating parent directories if needed.",
      inputSchema: {
        type: "object",
        properties: {
          path: { type: "string", description: "Absolute file path to write" },
          content: { type: "string", description: "File content" },
        },
        required: ["path", "content"],
      },
    },
    {
      name: "read_file",
      description: "Read a file and return its contents as a string.",
      inputSchema: {
        type: "object",
        properties: {
          path: { type: "string", description: "Absolute file path to read" },
        },
        required: ["path"],
      },
    },
    {
      name: "generate_secret",
      description: "Generate and return a random 64-character hex string.",
      inputSchema: { type: "object", properties: {} },
    },
    {
      name: "start_pm2_service",
      description:
        "Start a PM2 service. Shell scripts (.sh) run with bash interpreter.",
      inputSchema: {
        type: "object",
        properties: {
          name: { type: "string", description: "PM2 service name" },
          script: { type: "string", description: "Path to the script to run" },
          cwd: { type: "string", description: "Working directory for the service" },
        },
        required: ["name", "script", "cwd"],
      },
    },
    {
      name: "open_browser",
      description: "Open the user's default browser to a URL (macOS).",
      inputSchema: {
        type: "object",
        properties: {
          url: { type: "string", description: "URL to open" },
        },
        required: ["url"],
      },
    },
    {
      name: "send_channel_message",
      description: "POST a message to a Teletraan channel server.",
      inputSchema: {
        type: "object",
        properties: {
          url: {
            type: "string",
            description: "Channel server base URL (e.g. http://127.0.0.1:7100)",
          },
          secret: { type: "string", description: "Channel server secret" },
          conversationId: { type: "string", description: "Conversation ID" },
          text: { type: "string", description: "Message text" },
        },
        required: ["url", "secret", "conversationId", "text"],
      },
    },
    {
      name: "run_command",
      description:
        "Run a shell command. Working directory is the teletraan-framework root (run_command('pwd') returns it).",
      inputSchema: {
        type: "object",
        properties: {
          cmd: { type: "string", description: "Shell command to run" },
        },
        required: ["cmd"],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args = {} } = request.params;

  const ok = (text) => ({ content: [{ type: "text", text: String(text) }] });
  const fail = (msg) => ({
    content: [{ type: "text", text: `Error: ${msg}` }],
    isError: true,
  });

  try {
    switch (name) {
      // -----------------------------------------------------------------------
      case "resolve_claude_path": {
        try {
          const { stdout } = await execAsync("which claude", { timeout: 10_000 });
          const p = stdout.trim();
          if (p) return ok(p);
        } catch {}
        const candidates = [
          `${process.env.HOME}/.local/bin/claude`,
          "/usr/local/bin/claude",
          "/opt/homebrew/bin/claude",
        ];
        for (const c of candidates) {
          if (existsSync(c)) return ok(c);
        }
        return fail(
          "Could not locate the claude binary. Ensure Claude Code is installed and in PATH."
        );
      }

      // -----------------------------------------------------------------------
      case "create_directory": {
        mkdirSync(args.path, { recursive: true });
        return ok(`Created: ${args.path}`);
      }

      // -----------------------------------------------------------------------
      case "copy_directory": {
        if (!existsSync(args.src))
          return fail(`Source not found: ${args.src}`);
        mkdirSync(args.dest, { recursive: true });
        cpSync(args.src, args.dest, { recursive: true });
        return ok(`Copied ${args.src} → ${args.dest}`);
      }

      // -----------------------------------------------------------------------
      case "run_npm_install": {
        if (!existsSync(args.dir))
          return fail(`Directory not found: ${args.dir}`);
        const { stdout, stderr } = await execAsync("npm install", {
          cwd: args.dir,
          timeout: 300_000,
          env: { ...process.env, NEXT_TELEMETRY_DISABLED: "1" },
        });
        return ok(
          `npm install complete in ${args.dir}\n${(stdout + stderr).slice(-500)}`
        );
      }

      // -----------------------------------------------------------------------
      case "run_npm_build": {
        if (!existsSync(args.dir))
          return fail(`Directory not found: ${args.dir}`);
        const { stdout, stderr } = await execAsync("npm run build", {
          cwd: args.dir,
          timeout: 300_000,
          env: { ...process.env, NEXT_TELEMETRY_DISABLED: "1" },
        });
        return ok(
          `npm run build complete in ${args.dir}\n${(stdout + stderr).slice(-500)}`
        );
      }

      // -----------------------------------------------------------------------
      case "write_file": {
        const dir = dirname(resolve(args.path));
        mkdirSync(dir, { recursive: true });
        writeFileSync(args.path, args.content, "utf8");
        return ok(`Written: ${args.path}`);
      }

      // -----------------------------------------------------------------------
      case "read_file": {
        if (!existsSync(args.path))
          return fail(`File not found: ${args.path}`);
        return ok(readFileSync(args.path, "utf8"));
      }

      // -----------------------------------------------------------------------
      case "generate_secret": {
        return ok(randomBytes(32).toString("hex"));
      }

      // -----------------------------------------------------------------------
      case "start_pm2_service": {
        const { name: svcName, script, cwd } = args;
        const interpreterFlag = script.endsWith(".sh")
          ? "--interpreter bash"
          : "";
        const cmd = [
          "pm2 start",
          `"${script}"`,
          `--name "${svcName}"`,
          `--cwd "${cwd}"`,
          interpreterFlag,
          "&& pm2 save",
        ]
          .filter(Boolean)
          .join(" ");
        const { stdout, stderr } = await execAsync(cmd, { timeout: 60_000 });
        return ok(
          `PM2 service '${svcName}' started.\n${(stdout + stderr).trim()}`
        );
      }

      // -----------------------------------------------------------------------
      case "open_browser": {
        await execAsync(`open "${args.url}"`, { timeout: 10_000 });
        return ok(`Opened browser to ${args.url}`);
      }

      // -----------------------------------------------------------------------
      case "send_channel_message": {
        const { url, secret, conversationId, text } = args;
        const body = JSON.stringify({
          text,
          conversation_id: conversationId,
        });

        const responseText = await new Promise((resolve, reject) => {
          const target = new URL("/message", url);
          const mod = target.protocol === "https:" ? https : http;
          const req = mod.request(
            target.href,
            {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
                Authorization: `Bearer ${secret}`,
                "Content-Length": Buffer.byteLength(body),
              },
            },
            (res) => {
              let data = "";
              res.on("data", (chunk) => {
                data += chunk;
              });
              res.on("end", () => resolve(data));
            }
          );
          req.on("error", reject);
          req.write(body);
          req.end();
        });

        return ok(`Message sent. Response: ${responseText.trim()}`);
      }

      // -----------------------------------------------------------------------
      case "run_command": {
        const { stdout, stderr } = await execAsync(args.cmd, {
          cwd: ROOT,
          timeout: 120_000,
          shell: "/bin/bash",
        });
        return ok((stdout + stderr).trim());
      }

      // -----------------------------------------------------------------------
      default:
        return fail(`Unknown tool: ${name}`);
    }
  } catch (e) {
    return fail(e.message || String(e));
  }
});

const transport = new StdioServerTransport();
await server.connect(transport);
