import path from "node:path";
import fs from "node:fs";
import { fileURLToPath, pathToFileURL } from "node:url";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { getFixture } from "./tool-test-fixtures.js";
import { prepareMutation } from "./prepare-mutation.js";
import { executePendingActionConfirm } from "./pending-action-executor.js";
function isToolDefinition(value) {
  if (!value || typeof value !== "object") return false;
  const candidate = value;
  return typeof candidate.name === "string" && typeof candidate.description === "string" && candidate.inputSchema !== void 0 && typeof candidate.handler === "function";
}
function findGeneratedAiToolsPath() {
  const here = (() => {
    try {
      return fileURLToPath(import.meta.url);
    } catch {
      return null;
    }
  })();
  if (!here) return null;
  let cursor = path.dirname(here);
  for (let i = 0; i < 12; i++) {
    const candidate = path.join(cursor, "apps", "mercato", ".mercato", "generated", "ai-tools.generated.ts");
    if (fs.existsSync(candidate)) return candidate;
    const next = path.dirname(cursor);
    if (next === cursor) break;
    cursor = next;
  }
  const fromCwd = path.resolve(process.cwd(), "apps", "mercato", ".mercato", "generated", "ai-tools.generated.ts");
  if (fs.existsSync(fromCwd)) return fromCwd;
  const fromCwdDirect = path.resolve(process.cwd(), ".mercato", "generated", "ai-tools.generated.ts");
  if (fs.existsSync(fromCwdDirect)) return fromCwdDirect;
  return null;
}
async function compileAndImportGenerated(tsPath) {
  const jsPath = tsPath.replace(/\.ts$/, ".mjs");
  const appRoot = path.dirname(path.dirname(path.dirname(tsPath)));
  const tsExists = fs.existsSync(tsPath);
  if (!tsExists) {
    throw new Error(`Generated file not found: ${tsPath}`);
  }
  const jsExists = fs.existsSync(jsPath);
  const needsCompile = !jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs;
  if (needsCompile) {
    const esbuild = await import("esbuild");
    const tsSource = fs.readFileSync(tsPath, "utf-8");
    const aliasRewritten = tsSource.replace(
      /from\s+["']@\/([^"']+)["']/g,
      (_match, p1) => {
        const target = path.join(appRoot, p1);
        const candidate = fs.existsSync(target) ? target : fs.existsSync(target + ".ts") ? target + ".ts" : target;
        return `from ${JSON.stringify(pathToFileURL(candidate).href)}`;
      }
    ).replace(
      /import\s*\(\s*["']@\/([^"']+)["']\s*\)/g,
      (_match, p1) => {
        const target = path.join(appRoot, p1);
        const candidate = fs.existsSync(target) ? target : fs.existsSync(target + ".ts") ? target + ".ts" : target;
        return `import(${JSON.stringify(pathToFileURL(candidate).href)})`;
      }
    );
    const result = await esbuild.transform(aliasRewritten, {
      loader: "ts",
      format: "esm",
      target: "node18",
      sourcemap: false,
      sourcefile: tsPath
    });
    fs.writeFileSync(jsPath, result.code);
  }
  return await import(pathToFileURL(jsPath).href);
}
async function ensureApiRouteManifestsRegistered(generatedDir) {
  const tsPath = path.join(generatedDir, "api-routes.generated.ts");
  if (!fs.existsSync(tsPath)) return;
  try {
    const mod = await compileAndImportGenerated(tsPath);
    const apiRoutes = mod.apiRoutes;
    if (!Array.isArray(apiRoutes)) {
      console.warn("[tool-test-runner] api-routes.generated.mjs returned no apiRoutes array");
      return;
    }
    const registry = await import("@open-mercato/shared/modules/registry");
    registry.registerApiRouteManifests(
      apiRoutes
    );
    if (process.env.OM_TOOL_TEST_DEBUG === "1") {
      console.log(
        `[tool-test-runner] Registered ${apiRoutes.length} api-route manifests; getApiRouteManifests().length=${registry.getApiRouteManifests().length}`
      );
    }
  } catch (error) {
    console.warn(
      "[tool-test-runner] Could not register api-routes manifest:",
      error instanceof Error ? error.message : error
    );
  }
}
async function loadGeneratedTools() {
  const tsPath = findGeneratedAiToolsPath();
  if (!tsPath) {
    throw new Error(
      "Could not locate apps/<app>/.mercato/generated/ai-tools.generated.ts. Run `yarn generate` first."
    );
  }
  await ensureApiRouteManifestsRegistered(path.dirname(tsPath));
  const mod = await compileAndImportGenerated(tsPath);
  const entries = mod.aiToolConfigEntriesRaw ?? mod.aiToolConfigEntries ?? [];
  const result = [];
  for (const entry of entries) {
    if (!entry || typeof entry.moduleId !== "string") continue;
    const tools = Array.isArray(entry.tools) ? entry.tools.filter(isToolDefinition) : [];
    result.push({ moduleId: entry.moduleId, tools });
  }
  return result;
}
async function pickDefaultTenant(container) {
  try {
    const em = container.resolve("em");
    const conn = em.getConnection();
    const tenantRows = await conn.execute(
      `SELECT id FROM tenants WHERE deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`
    );
    const tenantId = Array.isArray(tenantRows) && tenantRows[0] ? String(tenantRows[0].id) : null;
    if (!tenantId) return null;
    const escaped = tenantId.replace(/'/g, "''");
    const orgRows = await conn.execute(
      `SELECT id FROM organizations WHERE tenant_id = '${escaped}' AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`
    );
    const organizationId = Array.isArray(orgRows) && orgRows[0] ? String(orgRows[0].id) : null;
    const userRows = await conn.execute(
      `SELECT id FROM users WHERE tenant_id = '${escaped}' AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1`
    );
    const userId = Array.isArray(userRows) && userRows[0] ? String(userRows[0].id) : null;
    return { tenantId, organizationId, userId };
  } catch (error) {
    if (process.env.OM_TOOL_TEST_DEBUG === "1") {
      console.warn("[tool-test-runner] pickDefaultTenant failed:", error);
    }
    return null;
  }
}
function buildSuperAdminContext(container, tenantId, organizationId, userId) {
  return {
    tenantId,
    organizationId,
    userId,
    container,
    userFeatures: ["*"],
    isSuperAdmin: true
  };
}
function clipPreview(value) {
  try {
    const json = JSON.stringify(value);
    if (json.length <= 400) return value;
    return `${json.slice(0, 400)}\u2026(truncated, ${json.length} bytes)`;
  } catch {
    return "[unserializable]";
  }
}
function dummyAgentForTool(tool) {
  const policy = "destructive-confirm-required";
  return {
    id: `__test__.${tool.name}`,
    moduleId: tool.name.split(".")[0] ?? "__test__",
    label: "Tool Test Runner",
    description: "Synthetic agent used by the tool test runner",
    systemPrompt: "",
    allowedTools: [tool.name],
    readOnly: false,
    mutationPolicy: policy
  };
}
async function executeReadTool(tool, args, container, ctx) {
  const fresh = await createRequestContainer();
  const freshCtx = { ...ctx, container: fresh, tool };
  void container;
  return tool.handler(args, freshCtx);
}
async function executeMutationTool(tool, args, container, ctx) {
  const fresh = await createRequestContainer();
  const agent = dummyAgentForTool(tool);
  const userId = ctx.userId ?? "00000000-0000-0000-0000-000000000000";
  const { uiPart, pendingAction } = await prepareMutation(
    {
      agent,
      tool,
      toolCallArgs: args,
      conversationId: null,
      mutationPolicyOverride: null
    },
    {
      tenantId: ctx.tenantId,
      organizationId: ctx.organizationId,
      userId,
      features: ctx.userFeatures,
      isSuperAdmin: ctx.isSuperAdmin,
      container: fresh
    }
  );
  const confirmTenantId = ctx.tenantId;
  const confirmContainer = await createRequestContainer();
  const confirmation = await executePendingActionConfirm({
    action: pendingAction,
    agent,
    tool,
    ctx: {
      tenantId: confirmTenantId,
      organizationId: ctx.organizationId,
      userId,
      container: confirmContainer,
      userFeatures: ctx.userFeatures,
      isSuperAdmin: ctx.isSuperAdmin
    },
    emitEvent: async () => {
    }
  });
  if (!confirmation.ok) {
    const error = confirmation.executionResult?.error;
    const cause = confirmation.cause;
    const causeMessage = cause instanceof Error ? cause.message : typeof cause === "string" ? cause : null;
    const message = error?.message ?? causeMessage ?? "handler invocation failed";
    throw new Error(message);
  }
  return {
    status: "pending-confirmation",
    pendingActionId: pendingAction.id,
    expiresAt: pendingAction.expiresAt.toISOString(),
    uiPartType: uiPart?.type ?? null,
    handlerExecuted: true
  };
}
async function resolveFixtureInput(fixture, resolved, container, ctx) {
  if (fixture.input) return { ok: true, input: { ...fixture.input } };
  if (!fixture.idFrom) {
    return { ok: false, reason: "fixture has neither input nor idFrom" };
  }
  const sourceTool = resolved.get(fixture.idFrom);
  if (!sourceTool) {
    return { ok: false, reason: `idFrom tool not found: ${fixture.idFrom}` };
  }
  const sourceFixture = getFixture(fixture.idFrom);
  if (!sourceFixture?.input) {
    return {
      ok: false,
      reason: `idFrom source ${fixture.idFrom} has no static input fixture`
    };
  }
  let listResult;
  try {
    listResult = await executeReadTool(
      sourceTool,
      { ...sourceFixture.input },
      container,
      ctx
    );
  } catch (error) {
    return {
      ok: false,
      reason: `idFrom source ${fixture.idFrom} threw: ${error instanceof Error ? error.message : String(error)}`
    };
  }
  const records = extractRecords(listResult);
  if (!records.length) {
    return {
      ok: false,
      reason: `idFrom source ${fixture.idFrom} returned no records`
    };
  }
  const idField = "id";
  const id = records[0][idField];
  if (typeof id !== "string" || !id) {
    return {
      ok: false,
      reason: `idFrom source ${fixture.idFrom} first record has no string id`
    };
  }
  const bindAs = fixture.bindAs ?? "id";
  return {
    ok: true,
    input: { [bindAs]: id, ...fixture.extra ?? {} }
  };
}
function extractRecords(value) {
  if (!value || typeof value !== "object") return [];
  const candidate = value;
  for (const key of ["records", "items", "people", "companies", "deals", "results", "data"]) {
    const arr = candidate[key];
    if (Array.isArray(arr)) return arr;
  }
  return [];
}
async function runToolTests(options = {}) {
  const includeMutations = options.includeMutations ?? true;
  const container = await createRequestContainer();
  let tenantId = options.tenantId ?? null;
  let organizationId = options.organizationId ?? null;
  let userId = options.userId ?? null;
  if (!tenantId) {
    const picked = await pickDefaultTenant(container);
    if (picked) {
      tenantId = picked.tenantId;
      organizationId = picked.organizationId;
      userId = userId ?? picked.userId;
    }
  }
  const ctx = buildSuperAdminContext(container, tenantId, organizationId, userId);
  const grouped = await loadGeneratedTools();
  const flat = [];
  for (const group of grouped) {
    if (options.moduleFilter && group.moduleId !== options.moduleFilter) continue;
    for (const tool of group.tools) flat.push({ moduleId: group.moduleId, tool });
  }
  const resolved = /* @__PURE__ */ new Map();
  for (const { tool } of flat) resolved.set(tool.name, tool);
  const records = [];
  for (const { moduleId, tool } of flat) {
    const start = Date.now();
    const isMutation = tool.isMutation === true;
    const fixture = getFixture(tool.name);
    if (!fixture) {
      records.push({
        module: moduleId,
        tool: tool.name,
        isMutation,
        status: "skip",
        durationMs: Date.now() - start,
        reason: "no fixture"
      });
      continue;
    }
    if (fixture.skip) {
      records.push({
        module: moduleId,
        tool: tool.name,
        isMutation,
        status: "skip",
        durationMs: Date.now() - start,
        reason: fixture.note ?? "skip-by-fixture"
      });
      continue;
    }
    if (isMutation && !includeMutations) {
      records.push({
        module: moduleId,
        tool: tool.name,
        isMutation,
        status: "skip",
        durationMs: Date.now() - start,
        reason: "mutations excluded"
      });
      continue;
    }
    const inputResult = await resolveFixtureInput(fixture, resolved, container, ctx);
    if (!inputResult.ok) {
      records.push({
        module: moduleId,
        tool: tool.name,
        isMutation,
        status: "skip",
        durationMs: Date.now() - start,
        reason: inputResult.reason
      });
      continue;
    }
    try {
      const result = isMutation ? await executeMutationTool(tool, inputResult.input, container, ctx) : await executeReadTool(tool, inputResult.input, container, ctx);
      JSON.stringify(result);
      if (isMutation && (typeof result !== "object" || result === null || result.status !== "pending-confirmation")) {
        records.push({
          module: moduleId,
          tool: tool.name,
          isMutation,
          status: "fail",
          durationMs: Date.now() - start,
          reason: "mutation tool did not route through prepareMutation (no pending-confirmation envelope)",
          resultPreview: clipPreview(result)
        });
        continue;
      }
      records.push({
        module: moduleId,
        tool: tool.name,
        isMutation,
        status: "pass",
        durationMs: Date.now() - start,
        resultPreview: clipPreview(result)
      });
    } catch (error) {
      records.push({
        module: moduleId,
        tool: tool.name,
        isMutation,
        status: "fail",
        durationMs: Date.now() - start,
        reason: error instanceof Error ? error.message : String(error)
      });
    }
  }
  const passed = records.filter((r) => r.status === "pass").length;
  const failed = records.filter((r) => r.status === "fail").length;
  const skipped = records.filter((r) => r.status === "skip").length;
  return {
    tenantId,
    organizationId,
    total: records.length,
    passed,
    failed,
    skipped,
    records
  };
}
export {
  runToolTests
};
//# sourceMappingURL=tool-test-runner.js.map
