import { existsSync, readFileSync, readdirSync } from "node:fs";
import { extname, join, relative, resolve } from "node:path";

export interface McpTool {
  name: string;
  description: string;
  inputSchema: {
    type: "object";
    properties?: Record<string, unknown>;
    additionalProperties?: boolean;
  };
}
export interface McpServerOptions {
  maxFiles?: number;
  maxFileBytes?: number;
  fetch?: typeof fetch;
  devServerUrl?: string;
  runtimeErrors?: () => unknown[] | Promise<unknown[]>;
}
export interface McpServer {
  tools(): McpTool[];
  call(name: string, args?: Record<string, unknown>): Promise<unknown>;
  handle(message: unknown): Promise<Record<string, unknown> | null>;
}

const TOOLS: McpTool[] = [
  {
    name: "wrnexus_routes",
    description: "List current application routes and source files.",
    inputSchema: { type: "object", additionalProperties: false },
  },
  {
    name: "wrnexus_components",
    description: "List components with declared props and public events.",
    inputSchema: { type: "object", additionalProperties: false },
  },
  {
    name: "wrnexus_database_schema",
    description: "Read bounded database schema and migration sources.",
    inputSchema: { type: "object", additionalProperties: false },
  },
  {
    name: "wrnexus_diagnostics",
    description: "Read current compiler diagnostics and build report.",
    inputSchema: { type: "object", additionalProperties: false },
  },
  {
    name: "wrnexus_runtime_errors",
    description: "Read captured runtime errors without secrets.",
    inputSchema: { type: "object", additionalProperties: false },
  },
  {
    name: "wrnexus_dev_status",
    description: "Check development server liveness and readiness.",
    inputSchema: { type: "object", additionalProperties: false },
  },
  {
    name: "wrnexus_docs",
    description: "List framework documentation or read one documentation file.",
    inputSchema: {
      type: "object",
      properties: { file: { type: "string" } },
      additionalProperties: false,
    },
  },
  {
    name: "wrnexus_packages",
    description: "List installed WRNexus packages and versions.",
    inputSchema: { type: "object", additionalProperties: false },
  },
];

function inside(root: string, path: string): boolean {
  const rel = relative(root, path);
  return rel === "" || (!rel.startsWith("..") && !resolve(rel).startsWith(".."));
}
function walk(
  root: string,
  directories: string[],
  options: Required<Pick<McpServerOptions, "maxFiles" | "maxFileBytes">>,
  extensions?: string[],
) {
  const output: Array<{ file: string; content: string }> = [];
  const visit = (directory: string) => {
    if (!existsSync(directory) || output.length >= options.maxFiles) return;
    for (const entry of readdirSync(directory, { withFileTypes: true })) {
      if (
        output.length >= options.maxFiles ||
        entry.name.startsWith(".") ||
        entry.name === "node_modules" ||
        entry.name === "dist"
      )
        continue;
      const path = join(directory, entry.name);
      if (entry.isDirectory()) visit(path);
      else if (!extensions || extensions.includes(extname(entry.name)))
        output.push({
          file: relative(root, path).replace(/\\/g, "/"),
          content: readFileSync(path, "utf8").slice(0, options.maxFileBytes),
        });
    }
  };
  for (const directory of directories) visit(resolve(root, directory));
  return output;
}
function routePath(file: string) {
  const value = file
    .replace(/^app\/(pages|api)\//, "")
    .replace(/\.(wrn|tsx?|jsx?)$/, "")
    .replace(/\/index$/, "")
    .replace(/\[\.\.\.(\w+)\]/g, "*$1")
    .replace(/\[(\w+)\]/g, ":$1");
  if (value === "index") return "/";
  return `/${value}`.replace(/\/$/, "") || "/";
}
function componentContract(content: string) {
  return {
    props: [...content.matchAll(/^\s*prop\s+([A-Za-z_$][\w$]*)/gm)].map((match) => match[1]),
    events: [...content.matchAll(/^\s*(?:event|emit)\s+([A-Za-z_$][\w$.-]*)/gm)].map(
      (match) => match[1],
    ),
  };
}
function redact(value: unknown): unknown {
  if (Array.isArray(value)) return value.map(redact);
  if (value && typeof value === "object")
    return Object.fromEntries(
      Object.entries(value).map(([key, item]) => [
        /token|secret|password|authorization|cookie/i.test(key) ? key : key,
        /token|secret|password|authorization|cookie/i.test(key) ? "[REDACTED]" : redact(item),
      ]),
    );
  return typeof value === "string" ? value.slice(0, 4_000) : value;
}

export function createFrameworkMcpServer(
  appRoot: string,
  options: McpServerOptions = {},
): McpServer {
  const root = resolve(appRoot);
  const limits = {
    maxFiles: options.maxFiles ?? 200,
    maxFileBytes: options.maxFileBytes ?? 128 * 1024,
  };
  const files = (directories: string[], extensions?: string[]) =>
    walk(root, directories, limits, extensions);
  const call = async (name: string, args: Record<string, unknown> = {}): Promise<unknown> => {
    if (name === "wrnexus_routes")
      return files(["app/pages", "app/api"], [".wrn", ".ts", ".tsx", ".js", ".jsx"])
        .map(({ file }) => ({
          path: routePath(file),
          kind: file.startsWith("app/api/") ? "api" : "page",
          file,
        }))
        .sort((left, right) => left.path.localeCompare(right.path));
    if (name === "wrnexus_components")
      return files(["app/components"], [".wrn"]).map(({ file, content }) => ({
        name: file
          .split("/")
          .at(-1)!
          .replace(/\.wrn$/, ""),
        file,
        ...componentContract(content),
      }));
    if (name === "wrnexus_database_schema") return files(["app/db"], [".sql", ".ts", ".json"]);
    if (name === "wrnexus_diagnostics")
      return files([".wrnexus", "dist"], [".json"])
        .filter((entry) => /diagnostic|report|contract/i.test(entry.file))
        .map((entry) => ({
          file: entry.file,
          data: (() => {
            try {
              return redact(JSON.parse(entry.content));
            } catch {
              return entry.content;
            }
          })(),
        }));
    if (name === "wrnexus_runtime_errors") return redact((await options.runtimeErrors?.()) ?? []);
    if (name === "wrnexus_dev_status") {
      const base = (options.devServerUrl ?? "http://localhost:3000").replace(/\/$/, "");
      const check = async (path: string) => {
        try {
          const response = await (options.fetch ?? fetch)(`${base}${path}`);
          return { status: response.status, ok: response.ok };
        } catch (error) {
          return {
            status: 0,
            ok: false,
            error: error instanceof Error ? error.message : "Unavailable",
          };
        }
      };
      return { url: base, liveness: await check("/healthz"), readiness: await check("/readyz") };
    }
    if (name === "wrnexus_docs") {
      const docs = files(["docs"], [".md"]);
      if (typeof args.file !== "string") return docs.map(({ file }) => file);
      const path = resolve(root, args.file);
      if (!inside(root, path) || !path.startsWith(resolve(root, "docs")) || !existsSync(path))
        throw new Error(
          "WRN-MCP-DOC-PATH: documentation file is outside the application docs directory.",
        );
      return {
        file: relative(root, path).replace(/\\/g, "/"),
        content: readFileSync(path, "utf8").slice(0, limits.maxFileBytes),
      };
    }
    if (name === "wrnexus_packages") {
      const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
        dependencies?: Record<string, string>;
        devDependencies?: Record<string, string>;
      };
      return Object.entries({ ...manifest.dependencies, ...manifest.devDependencies })
        .filter(([packageName]) => packageName.startsWith("@wrnexus/"))
        .map(([packageName, version]) => ({ name: packageName, version }))
        .sort((a, b) => a.name.localeCompare(b.name));
    }
    throw new Error(`WRN-MCP-TOOL: unknown tool '${name}'.`);
  };
  return {
    tools: () => TOOLS.map((tool) => ({ ...tool })),
    call,
    async handle(message) {
      if (!message || typeof message !== "object")
        return { jsonrpc: "2.0", id: null, error: { code: -32600, message: "Invalid Request" } };
      const request = message as { id?: unknown; method?: unknown; params?: any };
      if (request.method === "notifications/initialized") return null;
      if (request.method === "initialize")
        return {
          jsonrpc: "2.0",
          id: request.id ?? null,
          result: {
            protocolVersion: "2025-03-26",
            capabilities: { tools: { listChanged: false } },
            serverInfo: { name: "wrnexus", version: "0.8.0" },
          },
        };
      if (request.method === "tools/list")
        return { jsonrpc: "2.0", id: request.id ?? null, result: { tools: TOOLS } };
      if (request.method === "tools/call") {
        try {
          const value = await call(
            String(request.params?.name ?? ""),
            request.params?.arguments ?? {},
          );
          return {
            jsonrpc: "2.0",
            id: request.id ?? null,
            result: {
              content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
              structuredContent: { value },
            },
          };
        } catch (error) {
          return {
            jsonrpc: "2.0",
            id: request.id ?? null,
            result: {
              isError: true,
              content: [
                { type: "text", text: error instanceof Error ? error.message : "Tool failed" },
              ],
            },
          };
        }
      }
      return {
        jsonrpc: "2.0",
        id: request.id ?? null,
        error: { code: -32601, message: "Method not found" },
      };
    },
  };
}
