import { createReadStream } from "node:fs";
import { stat } from "node:fs/promises";

const MCP_PROTOCOL_VERSION = "2025-06-18";
const MAX_BODY_BYTES = 1024 * 1024;

function sendJson(response, statusCode, payload, headers = {}) {
  response.writeHead(statusCode, {
    "cache-control": "no-store",
    "content-type": "application/json; charset=utf-8",
    ...headers,
  });
  response.end(JSON.stringify(payload));
}

async function readJsonBody(request, maxBytes = MAX_BODY_BYTES) {
  const chunks = [];
  let size = 0;
  for await (const chunk of request) {
    size += chunk.length;
    if (size > maxBytes) throw new Error("payload_too_large");
    chunks.push(chunk);
  }
  if (!chunks.length) return {};
  return JSON.parse(Buffer.concat(chunks).toString("utf8"));
}

function bearerToken(request) {
  const match = String(request.headers.authorization || "").match(/^Bearer\s+(.+)$/i);
  return match?.[1]?.trim() || "";
}

function publicOrigin(request, configuredOrigin) {
  if (configuredOrigin) return String(configuredOrigin).replace(/\/+$/, "");
  const forwardedProto = String(request.headers["x-forwarded-proto"] || "").split(",")[0].trim();
  const protocol = forwardedProto === "https" ? "https" : "http";
  const forwardedHost = String(request.headers["x-forwarded-host"] || "").split(",")[0].trim();
  const host = forwardedHost || String(request.headers.host || "127.0.0.1");
  return `${protocol}://${host}`.replace(/\/+$/, "");
}

function actorFromProfile(profile) {
  const ownerId = String(profile?.id || "").trim();
  const ownerKey = String(profile?.email || ownerId).trim().toLowerCase();
  if (!ownerId || !ownerKey) throw new Error("foundry_agent_owner_required");
  return { ownerId, ownerKey };
}

function errorCode(error) {
  return String(error?.message || error || "foundry_agent_operation_failed").slice(0, 160);
}

function managementMatch(pathname) {
  const match = pathname.match(/^\/api\/foundry-agent-api\/agents(?:\/([^/]+)(?:\/(revoke|setup-code))?)?$/);
  if (!match) return null;
  return { agentId: match[1] ? decodeURIComponent(match[1]) : null, action: match[2] || null };
}

export function createFoundryAgentGateway({
  store,
  configuredPublicOrigin = "",
  installerPackagePath,
  ontologyCoreUrl = "",
  ontologyCoreAccessToken = "",
}) {
  if (!store) throw new Error("foundry_agent_store_required");

  const ontologyUrl = String(ontologyCoreUrl || "").trim().replace(/\/+$/, "");
  const ontologyToken = String(ontologyCoreAccessToken || "").trim();

  async function handleInstaller(request, response, url) {
    if (url.pathname !== "/api/foundry-agent/install/nodedc-foundry-codex-agent-setup.tgz") return false;
    if (request.method !== "GET") {
      response.writeHead(405, { allow: "GET" });
      response.end();
      return true;
    }
    try {
      const info = await stat(installerPackagePath);
      response.writeHead(200, {
        "cache-control": "no-store",
        "content-disposition": 'attachment; filename="nodedc-foundry-codex-agent-setup.tgz"',
        "content-length": info.size,
        "content-type": "application/octet-stream",
      });
      createReadStream(installerPackagePath).pipe(response);
    } catch {
      sendJson(response, 404, { ok: false, error: "foundry_agent_installer_not_found" });
    }
    return true;
  }

  async function handleSetupRedeem(request, response, url) {
    if (url.pathname !== "/api/foundry-agent/v1/setup/redeem") return false;
    if (request.method !== "POST") {
      response.writeHead(405, { allow: "POST" });
      response.end();
      return true;
    }
    try {
      const input = await readJsonBody(request, 16 * 1024);
      const redeemed = await store.redeemSetupCode(input.code, input.deviceName);
      const origin = publicOrigin(request, configuredPublicOrigin);
      sendJson(response, 200, {
        ok: true,
        agent: redeemed.agent,
        device: redeemed.device,
        foundry: {
          serverName: "nodedc_module_foundry",
          mcpUrl: `${origin}/api/mcp`,
          token: redeemed.foundryToken,
        },
        ontology: {
          serverName: "nodedc_ontology",
          mcpUrl: `${origin}/api/ontology-mcp`,
          token: redeemed.ontologyToken,
          readOnly: true,
        },
      });
    } catch (error) {
      const code = errorCode(error);
      sendJson(response, code === "payload_too_large" ? 413 : 400, { ok: false, error: code });
    }
    return true;
  }

  async function handleDoctor(request, response, url) {
    if (url.pathname !== "/api/foundry-agent/v1/doctor") return false;
    if (request.method !== "GET") {
      response.writeHead(405, { allow: "GET" });
      response.end();
      return true;
    }
    const session = await store.authenticateFoundryToken(bearerToken(request), { check: true });
    if (!session) {
      sendJson(response, 401, { ok: false, error: "foundry_agent_unauthorized" });
      return true;
    }
    sendJson(response, 200, {
      ok: true,
      agent: session.agent,
      device: session.device,
      foundryMcpUrl: `${publicOrigin(request, configuredPublicOrigin)}/api/mcp`,
      ontologyMcpUrl: `${publicOrigin(request, configuredPublicOrigin)}/api/ontology-mcp`,
      ontologyReadOnly: true,
    });
    return true;
  }

  async function handlePublicRequest(request, response, url) {
    return await handleInstaller(request, response, url)
      || await handleSetupRedeem(request, response, url)
      || await handleDoctor(request, response, url);
  }

  async function handleManagementRequest(request, response, url, profile) {
    const match = managementMatch(url.pathname);
    if (!match) return false;
    const owner = actorFromProfile(profile);
    try {
      if (!match.agentId && request.method === "GET") {
        sendJson(response, 200, { ok: true, agents: await store.listAgents(owner.ownerKey) });
        return true;
      }
      if (!match.agentId && request.method === "POST") {
        const input = await readJsonBody(request, 640 * 1024);
        const agent = await store.createAgent({ ...owner, name: input.name, avatarUrl: input.avatarUrl });
        sendJson(response, 201, { ok: true, agent });
        return true;
      }
      if (match.agentId && !match.action && request.method === "PATCH") {
        const input = await readJsonBody(request, 640 * 1024);
        const agent = await store.updateAgent(owner.ownerKey, match.agentId, input);
        sendJson(response, 200, { ok: true, agent });
        return true;
      }
      if (match.agentId && match.action === "revoke" && request.method === "POST") {
        const agent = await store.revokeAgent(owner.ownerKey, match.agentId);
        sendJson(response, 200, { ok: true, agent });
        return true;
      }
      if (match.agentId && match.action === "setup-code" && request.method === "POST") {
        const setup = await store.issueSetupCode(owner.ownerKey, match.agentId);
        const origin = publicOrigin(request, configuredPublicOrigin);
        const packageUrl = `${origin}/api/foundry-agent/install/nodedc-foundry-codex-agent-setup.tgz`;
        const command = `npm exec --yes --package=${packageUrl} nodedc-foundry-codex-agent -- --server ${origin} --code ${setup.code}`;
        sendJson(response, 201, { ok: true, install: { command, expiresAt: setup.expiresAt } });
        return true;
      }
      response.writeHead(405, { allow: "GET, POST, PATCH" });
      response.end();
    } catch (error) {
      const code = errorCode(error);
      const status = code === "foundry_agent_not_found" ? 404 : code === "payload_too_large" ? 413 : 400;
      sendJson(response, status, { ok: false, error: code });
    }
    return true;
  }

  async function authenticateFoundryToken(token, options) {
    const session = await store.authenticateFoundryToken(token, options);
    return session?.actor || null;
  }

  async function handleOntologyMcp(request, response, url) {
    if (url.pathname !== "/api/ontology-mcp") return false;
    if (request.method !== "POST") {
      response.writeHead(405, { allow: "POST" });
      response.end();
      return true;
    }
    const session = await store.authenticateOntologyToken(bearerToken(request), { check: true });
    if (!session) {
      sendJson(response, 401, { ok: false, error: "ontology_agent_unauthorized" });
      return true;
    }
    if (!ontologyUrl || !ontologyToken) {
      sendJson(response, 503, { ok: false, error: "ontology_agent_proxy_not_configured" });
      return true;
    }
    let input;
    try {
      input = await readJsonBody(request);
    } catch (error) {
      sendJson(response, error?.message === "payload_too_large" ? 413 : 400, { ok: false, error: errorCode(error) });
      return true;
    }
    let upstream;
    try {
      upstream = await fetch(`${ontologyUrl}/mcp`, {
        method: "POST",
        redirect: "manual",
        headers: {
          accept: String(request.headers.accept || "application/json, text/event-stream"),
          authorization: `Bearer ${ontologyToken}`,
          "content-type": "application/json",
          "mcp-protocol-version": String(request.headers["mcp-protocol-version"] || MCP_PROTOCOL_VERSION),
        },
        body: JSON.stringify(input),
        signal: AbortSignal.timeout(60_000),
      });
    } catch {
      sendJson(response, 503, { ok: false, error: "ontology_agent_proxy_unavailable" });
      return true;
    }
    const payload = await upstream.text();
    response.statusCode = upstream.status;
    response.setHeader("cache-control", "no-store");
    response.setHeader("content-type", upstream.headers.get("content-type") || "application/json; charset=utf-8");
    for (const header of ["mcp-protocol-version", "mcp-session-id", "vary"]) {
      const value = upstream.headers.get(header);
      if (value) response.setHeader(header, value);
    }
    response.end(payload);
    return true;
  }

  return {
    handlePublicRequest,
    handleManagementRequest,
    handleOntologyMcp,
    authenticateFoundryToken,
    health() {
      return {
        configured: Boolean(installerPackagePath),
        ontologyProxyConfigured: Boolean(ontologyUrl && ontologyToken),
        tokenLifecycle: "durable-until-revoke",
      };
    },
  };
}
