package agent

import (
	"fmt"
	"os"
	"path/filepath"
)

// writePiMCPBridge materialises the pi MCP bridge TS extension into a
// launch-scoped directory and returns its absolute path. It must not be placed
// in PI_CODING_AGENT_DIR: the bridge is auxiliary launch state, unlike
// provider/auth/model files.
//
// The bridge is version-locked to the daemon binary that wrote it (the full
// extension source is embedded below). It reads the daemon's HTTP MCP server
// descriptors from the CHANNEL9_MCP_SERVERS env var, initializes each server,
// lists its tools, and registers every tool with pi so the model can call AgentTeam
// tools through the normal pi tool set.
func writePiMCPBridge(workDir string) (string, error) {
	dir := filepath.Clean(workDir)
	if dir == "" || dir == "." {
		return "", fmt.Errorf("pi: workdir is required to materialise MCP bridge")
	}
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return "", fmt.Errorf("pi: ensure workdir for bridge: %w", err)
	}
	path := filepath.Join(dir, "mcp-bridge.ts")
	if err := writePiModelsAtomic(path, []byte(piMCPBridgeExtensionCode)); err != nil {
		return "", fmt.Errorf("pi: write bridge file %s: %w", path, err)
	}
	return path, nil
}

// piMCPBridgeExtensionCode is the TypeScript extension pi loads via `-e`.
//
// The bridge:
//   - reads CHANNEL9_MCP_SERVERS from env (JSON array of {name, url, headers})
//   - on session_start, for each server: initialize -> notifications/initialized
//     -> tools/list, then registers every advertised tool with pi
//   - tool calls round-trip via HTTP POST tools/call to the same URL
//
// All transport is JSON-RPC POST against the daemon's HTTP MCP endpoint,
// matching the wire format AgentTeam uses for the local chat MCP server.
const piMCPBridgeExtensionCode = `// agentteam-pi-mcp-bridge
// Generated by desktop/internal/agent/driver_pi_mcp.go.
// Do not edit in place — regenerate from daemon source.

interface ServerDescriptor {
  name: string;
  url: string;
  headers?: Record<string, string>;
}

interface JsonRpcResponse {
  jsonrpc: "2.0";
  id?: number | string;
  result?: any;
  error?: { code: number; message: string; data?: any };
}

const SERVERS_ENV = "CHANNEL9_MCP_SERVERS";

function parseServers(): ServerDescriptor[] {
  const raw = process.env[SERVERS_ENV];
  if (!raw) return [];
  try {
    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) return [];
    return parsed.filter(
      (s: any) =>
        s && typeof s.name === "string" && typeof s.url === "string"
    ) as ServerDescriptor[];
  } catch {
    return [];
  }
}

function makePoster(server: ServerDescriptor) {
  let nextId = 1;
  return async function rpc(method: string, params: any = {}): Promise<JsonRpcResponse> {
    const id = nextId++;
    const headers: Record<string, string> = {
      "Content-Type": "application/json",
      Accept: "application/json",
      ...(server.headers ?? {}),
    };
    const body = JSON.stringify({ jsonrpc: "2.0", id, method, params });
    const res = await fetch(server.url, { method: "POST", headers, body });
    if (!res.ok) {
      throw new Error(
        "[agentteam-pi-mcp-bridge] " + server.name + " " + method + " -> HTTP " + res.status
      );
    }
    const text = await res.text();
    let parsed: JsonRpcResponse;
    try {
      parsed = JSON.parse(text);
    } catch (err) {
      throw new Error(
        "[agentteam-pi-mcp-bridge] " + server.name + " " + method + " -> non-JSON response: " + text.slice(0, 200)
      );
    }
    return parsed;
  };
}

async function notify(server: ServerDescriptor, method: string, params: any = {}) {
  try {
    const headers: Record<string, string> = {
      "Content-Type": "application/json",
      Accept: "application/json",
      ...(server.headers ?? {}),
    };
    await fetch(server.url, {
      method: "POST",
      headers,
      body: JSON.stringify({ jsonrpc: "2.0", method, params }),
    });
  } catch {
    // Notifications must not throw; daemon side ignores response anyway.
  }
}

async function registerServer(pi: any, server: ServerDescriptor) {
  const rpc = makePoster(server);
  const init = await rpc("initialize", {
    protocolVersion: "2025-03-26",
    capabilities: {},
    clientInfo: { name: "pi-agentteam-bridge", version: "1.0.0" },
  });
  if (init.error) {
    throw new Error(
      "[agentteam-pi-mcp-bridge] " + server.name + " initialize: " + (init.error.message || "error")
    );
  }
  await notify(server, "notifications/initialized");

  const list = await rpc("tools/list");
  if (list.error) {
    throw new Error(
      "[agentteam-pi-mcp-bridge] " + server.name + " tools/list: " + (list.error.message || "error")
    );
  }
  const tools: any[] = (list.result && list.result.tools) || [];
  for (const t of tools) {
    if (!t || typeof t.name !== "string") continue;
    // OpenAI / DeepSeek tool names must match ^[a-zA-Z0-9_-]+$. AgentTeam's MCP
    // catalogue uses dotted names (e.g. trigger.create), so replace any
    // disallowed character with "_" before registering with pi. Keep the
    // original (dotted) name for the daemon-side tools/call so the chat MCP
    // server still resolves it.
    const originalName = t.name;
    const sanitizedName = originalName.replace(/[^a-zA-Z0-9_-]/g, "_");
    pi.registerTool({
      name: sanitizedName,
      description: t.description || originalName,
      parameters: t.inputSchema || { type: "object", properties: {} },
      async execute(_id: string, params: any) {
        try {
          const r = await rpc("tools/call", { name: originalName, arguments: params });
          if (r.error) {
            return {
              content: [
                {
                  type: "text",
                  text: "Error from " + server.name + "/" + originalName + ": " + (r.error.message || "error"),
                },
              ],
              isError: true,
            };
          }
          const result = r.result || {};
          return {
            content: result.content || [{ type: "text", text: "OK" }],
            isError: !!result.isError,
          };
        } catch (err: any) {
          return {
            content: [
              {
                type: "text",
                text: "[agentteam-pi-mcp-bridge] " + server.name + "/" + originalName + " failed: " + (err?.message || err),
              },
            ],
            isError: true,
          };
        }
      },
    });
  }
}

export default function agentteamMcpBridge(pi: any) {
  const servers = parseServers();
  if (servers.length === 0) return;

  pi.on("session_start", async () => {
    for (const server of servers) {
      try {
        await registerServer(pi, server);
      } catch (err: any) {
        console.error(err?.message || err);
      }
    }
  });
}
`
