import fs from "node:fs";
import os from "node:os";
import path from "node:path";

export async function createChatPipelineHarness(prefix) {
  const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), `omniroute-${prefix}-`));
  process.env.DATA_DIR = testDataDir;
  process.env.REQUIRE_API_KEY = "false";
  // Disable dashboard auth so direct route handler calls don't get 401
  // (CI sets JWT_SECRET + INITIAL_PASSWORD, causing isAuthRequired() → true)
  process.env.DASHBOARD_PASSWORD = "";
  process.env.INITIAL_PASSWORD = "";
  delete process.env.JWT_SECRET;
  // FASE-01: API_KEY_SECRET is required for CRC operations (no hardcoded fallback)
  if (!process.env.API_KEY_SECRET) {
    process.env.API_KEY_SECRET = "test-harness-secret-" + Date.now();
  }

  const core = await import("../../src/lib/db/core.ts");
  const providersDb = await import("../../src/lib/db/providers.ts");
  const combosDb = await import("../../src/lib/db/combos.ts");
  const settingsDb = await import("../../src/lib/db/settings.ts");
  const apiKeysDb = await import("../../src/lib/db/apiKeys.ts");
  const modelComboMappingsDb = await import("../../src/lib/db/modelComboMappings.ts");
  const readCacheDb = await import("../../src/lib/db/readCache.ts");
  const memoryStore = await import("../../src/lib/memory/store.ts");
  const memoryToolsModule = await import("../../open-sse/mcp-server/tools/memoryTools.ts");
  const { invalidateMemorySettingsCache } = await import("../../src/lib/memory/settings.ts");
  const { skillRegistry } = await import("../../src/lib/skills/registry.ts");
  const { skillExecutor } = await import("../../src/lib/skills/executor.ts");
  const builtinsModule = await import("../../src/lib/skills/builtins.ts");
  const sandboxModule = await import("../../src/lib/skills/sandbox.ts");
  const skillsRouteModule = await import("../../src/app/api/skills/route.ts");
  const skillByIdRouteModule = await import("../../src/app/api/skills/[id]/route.ts");
  const idempotencyLayerModule = await import("../../src/lib/idempotencyLayer.ts");
  const semanticCacheModule = await import("../../src/lib/semanticCache.ts");
  const { handleChat } = await import("../../src/sse/handlers/chat.ts");
  const { initTranslators } = await import("../../open-sse/translator/index.ts");
  const { clearInflight } = await import("../../open-sse/services/requestDedup.ts");
  const { BaseExecutor } = await import("../../open-sse/executors/base.ts");
  const { resetAllCircuitBreakers } = await import("../../src/shared/utils/circuitBreaker.ts");

  const originalFetch = globalThis.fetch;
  const originalRetryDelayMs = BaseExecutor.RETRY_CONFIG.delayMs;

  function clearSkillState() {
    (skillRegistry as any).registeredSkills?.clear?.();
    (skillRegistry as any).versionCache?.clear?.();
    (skillExecutor as any).handlers?.clear?.();
  }

  function toPlainHeaders(headers) {
    if (!headers) return {};
    const plain = {};
    if (typeof headers.forEach === "function") {
      try {
        headers.forEach((value, key) => {
          plain[key.toLowerCase()] = value;
        });
        return plain;
      } catch (e) {
        // Fall through to other strategies if forEach fails due to cross-realm private slot errors
      }
    }
    if (typeof headers.entries === "function") {
      try {
        for (const [key, value] of headers.entries()) {
          plain[key.toLowerCase()] = value;
        }
        return plain;
      } catch (e) {
        // Fall through
      }
    }
    try {
      for (const [key, value] of Object.entries(headers)) {
        plain[key.toLowerCase()] = value == null ? "" : String(value);
      }
    } catch (e) {}
    return plain;
  }

  function buildRequest({
    url = "http://localhost/v1/chat/completions",
    body,
    authKey = null,
    headers = {},
  }: {
    url?: string;
    body?: any;
    authKey?: string | null;
    headers?: Record<string, string>;
  } = {}) {
    const requestHeaders: Record<string, string> = {
      "Content-Type": "application/json",
      ...headers,
    };

    if (authKey) {
      requestHeaders.Authorization = `Bearer ${authKey}`;
    }

    return new Request(url, {
      method: "POST",
      headers: requestHeaders,
      body: typeof body === "string" ? body : JSON.stringify(body),
    });
  }

  function buildOpenAIResponse(text = "ok", model = "gpt-4o-mini", usage = null) {
    return new Response(
      JSON.stringify({
        id: "chatcmpl_json",
        object: "chat.completion",
        model,
        choices: [
          {
            index: 0,
            message: { role: "assistant", content: text },
            finish_reason: "stop",
          },
        ],
        usage: usage || {
          prompt_tokens: 4,
          completion_tokens: 2,
          total_tokens: 6,
        },
      }),
      {
        status: 200,
        headers: { "Content-Type": "application/json" },
      }
    );
  }

  function buildOpenAIToolCallResponse({
    model = "gpt-4o-mini",
    toolName = "lookupWeather@1.0.0",
    toolCallId = "call_weather",
    argumentsObject = { location: "Sao Paulo" },
  } = {}) {
    return new Response(
      JSON.stringify({
        id: "chatcmpl_tool",
        object: "chat.completion",
        model,
        choices: [
          {
            index: 0,
            message: {
              role: "assistant",
              content: "",
              tool_calls: [
                {
                  id: toolCallId,
                  type: "function",
                  function: {
                    name: toolName,
                    arguments: JSON.stringify(argumentsObject),
                  },
                },
              ],
            },
            finish_reason: "tool_calls",
          },
        ],
        usage: {
          prompt_tokens: 6,
          completion_tokens: 4,
          total_tokens: 10,
        },
      }),
      {
        status: 200,
        headers: { "Content-Type": "application/json" },
      }
    );
  }

  function buildClaudeResponse(text = "ok", model = "claude-3-5-sonnet-20241022") {
    return new Response(
      JSON.stringify({
        id: "msg_json",
        type: "message",
        role: "assistant",
        model,
        content: [{ type: "text", text }],
        stop_reason: "end_turn",
        usage: {
          input_tokens: 10,
          output_tokens: 4,
        },
      }),
      {
        status: 200,
        headers: { "Content-Type": "application/json" },
      }
    );
  }

  function buildGeminiResponse(text = "ok", model = "gemini-2.5-flash") {
    return new Response(
      JSON.stringify({
        responseId: "resp_gemini",
        modelVersion: model,
        createTime: "2026-04-05T12:00:00.000Z",
        candidates: [
          {
            content: {
              parts: [{ text }],
            },
            finishReason: "STOP",
          },
        ],
        usageMetadata: {
          promptTokenCount: 5,
          candidatesTokenCount: 7,
          totalTokenCount: 12,
        },
      }),
      {
        status: 200,
        headers: { "Content-Type": "application/json" },
      }
    );
  }

  async function waitFor(fn, timeoutMs = 1500) {
    const startedAt = Date.now();
    while (Date.now() - startedAt < timeoutMs) {
      const result = await fn();
      if (result) return result;
      await new Promise((resolve) => setTimeout(resolve, 25));
    }
    return null;
  }

  async function resetStorage() {
    globalThis.fetch = originalFetch;
    clearInflight();
    idempotencyLayerModule.clearIdempotency();
    semanticCacheModule.clearCache();
    resetAllCircuitBreakers();
    apiKeysDb.resetApiKeyState();
    readCacheDb.invalidateDbCache();
    invalidateMemorySettingsCache();
    clearSkillState();
    await new Promise((resolve) => setTimeout(resolve, 20));
    core.resetDbInstance();
    fs.rmSync(testDataDir, { recursive: true, force: true });
    fs.mkdirSync(testDataDir, { recursive: true });
    initTranslators();
  }

  async function cleanup() {
    BaseExecutor.RETRY_CONFIG.delayMs = originalRetryDelayMs;
    globalThis.fetch = originalFetch;
    clearInflight();
    idempotencyLayerModule.clearIdempotency();
    semanticCacheModule.clearCache();
    clearSkillState();
    resetAllCircuitBreakers();
    core.resetDbInstance();
    fs.rmSync(testDataDir, { recursive: true, force: true });
  }

  async function seedConnection(provider: string, overrides: any = {}) {
    return providersDb.createProviderConnection({
      provider,
      authType: "apikey",
      name: overrides.name || `${provider}-primary`,
      apiKey: overrides.apiKey || `sk-${provider}-${crypto.randomUUID().slice(0, 8)}`,
      isActive: overrides.isActive ?? true,
      testStatus: overrides.testStatus || "active",
      priority: overrides.priority,
      rateLimitedUntil: overrides.rateLimitedUntil,
      providerSpecificData: overrides.providerSpecificData || {},
    });
  }

  async function seedApiKey({
    name = `${prefix}-key`,
    noLog = false,
    allowedConnections,
    allowedModels,
  }: {
    name?: string;
    noLog?: boolean;
    allowedConnections?: any;
    allowedModels?: any;
  } = {}) {
    const key = await apiKeysDb.createApiKey(name, "machine-test");
    const updates: any = {};
    if (noLog) updates.noLog = true;
    if (allowedConnections) updates.allowedConnections = allowedConnections;
    if (allowedModels) updates.allowedModels = allowedModels;
    if (Object.keys(updates).length > 0) {
      await apiKeysDb.updateApiKeyPermissions(key.id, updates);
    }
    return key;
  }

  initTranslators();

  return {
    TEST_DATA_DIR: testDataDir,
    BaseExecutor,
    apiKeysDb,
    buildClaudeResponse,
    buildGeminiResponse,
    buildOpenAIResponse,
    buildOpenAIToolCallResponse,
    buildRequest,
    builtinsModule,
    cleanup,
    combosDb,
    core,
    handleChat,
    memoryStore,
    memoryTools: memoryToolsModule.memoryTools,
    modelComboMappingsDb,
    originalRetryDelayMs,
    resetStorage,
    sandboxModule,
    idempotencyLayerModule,
    semanticCacheModule,
    seedApiKey,
    seedConnection,
    settingsDb,
    skillByIdRouteModule,
    skillExecutor,
    skillRegistry,
    skillsRouteModule,
    toPlainHeaders,
    waitFor,
  };
}
