import path from "node:path";
import { vi } from "vitest";
import { createGatewayConfigModuleMock } from "./test-helpers.config-runtime.js";
import {
  getTestPluginRegistry,
  resetTestPluginRegistry,
  setTestPluginRegistry,
} from "./test-helpers.plugin-registry.js";
import {
  agentCommand,
  cronIsolatedRun,
  dispatchInboundMessageMock,
  embeddedRunMock,
  type GetReplyFromConfigFn,
  getReplyFromConfig,
  getGatewayTestHoistedState,
  mockGetReplyFromConfigOnce,
  piSdkMock,
  runBtwSideQuestion,
  sendWhatsAppMock,
  sessionStoreSaveDelayMs,
  setTestConfigRoot,
  testIsNixMode,
  testState,
  testTailnetIPv4,
  testTailscaleWhois,
  type RunBtwSideQuestionFn,
} from "./test-helpers.runtime-state.js";

export { getTestPluginRegistry, resetTestPluginRegistry, setTestPluginRegistry };
export {
  agentCommand,
  cronIsolatedRun,
  dispatchInboundMessageMock,
  embeddedRunMock,
  getReplyFromConfig,
  mockGetReplyFromConfigOnce,
  piSdkMock,
  runBtwSideQuestion,
  sendWhatsAppMock,
  sessionStoreSaveDelayMs,
  setTestConfigRoot,
  testIsNixMode,
  testState,
  testTailnetIPv4,
  testTailscaleWhois,
};

function buildBundledPluginModuleId(pluginId: string, artifactBasename: string): string {
  return ["..", "..", "extensions", pluginId, artifactBasename].join("/");
}

const gatewayTestHoisted = getGatewayTestHoistedState();

function createEmbeddedRunMockExports() {
  return {
    compactEmbeddedPiSession: (...args: unknown[]) =>
      embeddedRunMock.compactEmbeddedPiSession(...args),
    isEmbeddedPiRunActive: (sessionId: string) => embeddedRunMock.activeIds.has(sessionId),
    abortEmbeddedPiRun: (sessionId: string) => {
      embeddedRunMock.abortCalls.push(sessionId);
      return embeddedRunMock.activeIds.has(sessionId);
    },
    waitForEmbeddedPiRunEnd: async (sessionId: string) => {
      embeddedRunMock.waitCalls.push(sessionId);
      return embeddedRunMock.waitResults.get(sessionId) ?? true;
    },
  };
}

async function importEmbeddedRunMockModule<TModule extends object>(
  actualPath: string,
  opts?: { includeActiveCount?: boolean },
): Promise<TModule> {
  const actual = await vi.importActual<TModule>(actualPath);
  return {
    ...actual,
    ...createEmbeddedRunMockExports(),
    ...(opts?.includeActiveCount
      ? { getActiveEmbeddedRunCount: () => embeddedRunMock.activeIds.size }
      : {}),
  };
}

vi.mock("../agents/pi-model-discovery.js", async () => {
  const actual = await vi.importActual<typeof import("../agents/pi-model-discovery.js")>(
    "../agents/pi-model-discovery.js",
  );

  const createActualRegistry = (...args: Parameters<typeof actual.discoverModels>) => {
    const modelsFile = path.join(args[1], "models.json");
    const Registry = actual.ModelRegistry as unknown as {
      create?: (
        authStorage: unknown,
        modelsFile: string,
      ) => {
        getAll: () => Array<{ provider?: string; id?: string }>;
        getAvailable: () => Array<{ provider?: string; id?: string }>;
        find: (provider: string, modelId: string) => unknown;
      };
      new (
        authStorage: unknown,
        modelsFile: string,
      ): {
        getAll: () => Array<{ provider?: string; id?: string }>;
        getAvailable: () => Array<{ provider?: string; id?: string }>;
        find: (provider: string, modelId: string) => unknown;
      };
    };
    if (typeof Registry.create === "function") {
      return Registry.create(args[0], modelsFile);
    }
    return new Registry(args[0], modelsFile);
  };

  class MockModelRegistry {
    private readonly actualRegistry?: ReturnType<typeof createActualRegistry>;

    constructor(authStorage: unknown, modelsFile: string) {
      if (!piSdkMock.enabled) {
        this.actualRegistry = createActualRegistry(authStorage as never, path.dirname(modelsFile));
      }
    }

    getAll() {
      if (!piSdkMock.enabled) {
        return this.actualRegistry?.getAll() ?? [];
      }
      piSdkMock.discoverCalls += 1;
      return piSdkMock.models as Array<{ provider?: string; id?: string }>;
    }

    getAvailable() {
      if (!piSdkMock.enabled) {
        return this.actualRegistry?.getAvailable() ?? [];
      }
      return piSdkMock.models as Array<{ provider?: string; id?: string }>;
    }

    find(provider: string, modelId: string) {
      if (!piSdkMock.enabled) {
        return this.actualRegistry?.find(provider, modelId);
      }
      return (piSdkMock.models as Array<{ provider?: string; id?: string }>).find(
        (model) => model.provider === provider && model.id === modelId,
      );
    }
  }

  return {
    ...actual,
    ModelRegistry: MockModelRegistry,
  };
});

vi.mock("../cron/isolated-agent.js", () => ({
  runCronIsolatedAgentTurn: (...args: unknown[]) =>
    (cronIsolatedRun as (...args: unknown[]) => unknown)(...args),
}));

vi.mock("../infra/tailnet.js", () => ({
  pickPrimaryTailnetIPv4: () => testTailnetIPv4.value,
  pickPrimaryTailnetIPv6: () => undefined,
}));

vi.mock("../infra/tailscale.js", async () => {
  const actual =
    await vi.importActual<typeof import("../infra/tailscale.js")>("../infra/tailscale.js");
  return {
    ...actual,
    readTailscaleWhoisIdentity: async () => testTailscaleWhois.value,
  };
});

vi.mock("../config/sessions.js", async () => {
  const actual =
    await vi.importActual<typeof import("../config/sessions.js")>("../config/sessions.js");
  return {
    ...actual,
    saveSessionStore: vi.fn(async (storePath: string, store: unknown) => {
      const delay = sessionStoreSaveDelayMs.value;
      if (delay > 0) {
        await new Promise((resolve) => setTimeout(resolve, delay));
      }
      return actual.saveSessionStore(storePath, store as never);
    }),
  };
});

vi.mock("../config/config.js", async () => {
  const actual = await vi.importActual<typeof import("../config/config.js")>("../config/config.js");
  return createGatewayConfigModuleMock(actual);
});

vi.mock("../config/io.js", async () => {
  const actual = await vi.importActual<typeof import("../config/io.js")>("../config/io.js");
  const configActual =
    await vi.importActual<typeof import("../config/config.js")>("../config/config.js");
  const configMock = createGatewayConfigModuleMock(configActual);
  const createConfigIO = vi.fn(() => ({
    ...actual.createConfigIO(),
    loadConfig: configMock.loadConfig,
    readConfigFileSnapshot: configMock.readConfigFileSnapshot,
    readConfigFileSnapshotForWrite: configMock.readConfigFileSnapshotForWrite,
    writeConfigFile: configMock.writeConfigFile,
  }));
  return {
    ...actual,
    createConfigIO,
    getRuntimeConfig: configMock.getRuntimeConfig,
    loadConfig: configMock.loadConfig,
    readConfigFileSnapshot: configMock.readConfigFileSnapshot,
    readConfigFileSnapshotForWrite: configMock.readConfigFileSnapshotForWrite,
    writeConfigFile: configMock.writeConfigFile,
  };
});

vi.mock("../agents/pi-embedded.js", async () => {
  return await importEmbeddedRunMockModule<typeof import("../agents/pi-embedded.js")>(
    "../agents/pi-embedded.js",
  );
});

vi.mock("/src/agents/pi-embedded.js", async () => {
  return await importEmbeddedRunMockModule<typeof import("../agents/pi-embedded.js")>(
    "../agents/pi-embedded.js",
  );
});

vi.mock("../agents/pi-embedded-runner/runs.js", async () => {
  return await importEmbeddedRunMockModule<typeof import("../agents/pi-embedded-runner/runs.js")>(
    "../agents/pi-embedded-runner/runs.js",
    { includeActiveCount: true },
  );
});

vi.mock("/src/agents/pi-embedded-runner/runs.js", async () => {
  return await importEmbeddedRunMockModule<typeof import("../agents/pi-embedded-runner/runs.js")>(
    "../agents/pi-embedded-runner/runs.js",
    { includeActiveCount: true },
  );
});

vi.mock("../commands/health.js", () => ({
  getHealthSnapshot: vi.fn().mockResolvedValue({ ok: true, stub: true }),
}));
vi.mock("../commands/status.js", () => ({
  getStatusSummary: vi.fn().mockResolvedValue({ ok: true }),
}));
vi.mock(buildBundledPluginModuleId("whatsapp", "runtime-api.js"), () => ({
  sendMessageWhatsApp: (...args: unknown[]) =>
    (gatewayTestHoisted.sendWhatsAppMock as (...args: unknown[]) => unknown)(...args),
  sendPollWhatsApp: (...args: unknown[]) =>
    (gatewayTestHoisted.sendWhatsAppMock as (...args: unknown[]) => unknown)(...args),
}));
vi.mock("../channels/web/index.js", async () => {
  const actual = await vi.importActual<typeof import("../channels/web/index.js")>(
    "../channels/web/index.js",
  );
  return {
    ...actual,
    sendMessageWhatsApp: (...args: unknown[]) =>
      (gatewayTestHoisted.sendWhatsAppMock as (...args: unknown[]) => unknown)(...args),
  };
});
vi.mock("../commands/agent.js", () => ({
  agentCommand,
  agentCommandFromIngress: agentCommand,
}));
vi.mock("../agents/btw.js", () => ({
  runBtwSideQuestion: (...args: Parameters<RunBtwSideQuestionFn>) =>
    gatewayTestHoisted.runBtwSideQuestion(...args),
}));
vi.mock("/src/agents/btw.js", () => ({
  runBtwSideQuestion: (...args: Parameters<RunBtwSideQuestionFn>) =>
    gatewayTestHoisted.runBtwSideQuestion(...args),
}));
vi.mock("../auto-reply/dispatch.js", async () => {
  const actual = await vi.importActual<typeof import("../auto-reply/dispatch.js")>(
    "../auto-reply/dispatch.js",
  );
  return {
    ...actual,
    dispatchInboundMessage: (...args: Parameters<typeof actual.dispatchInboundMessage>) => {
      const impl = gatewayTestHoisted.dispatchInboundMessage.getMockImplementation();
      return impl
        ? gatewayTestHoisted.dispatchInboundMessage(...args)
        : actual.dispatchInboundMessage(...args);
    },
  };
});
vi.mock("/src/auto-reply/dispatch.js", async () => {
  const actual = await vi.importActual<typeof import("../auto-reply/dispatch.js")>(
    "../auto-reply/dispatch.js",
  );
  return {
    ...actual,
    dispatchInboundMessage: (...args: Parameters<typeof actual.dispatchInboundMessage>) => {
      const impl = gatewayTestHoisted.dispatchInboundMessage.getMockImplementation();
      return impl
        ? gatewayTestHoisted.dispatchInboundMessage(...args)
        : actual.dispatchInboundMessage(...args);
    },
  };
});
vi.mock("../auto-reply/reply.js", () => ({
  getReplyFromConfig: (...args: Parameters<GetReplyFromConfigFn>) =>
    gatewayTestHoisted.getReplyFromConfig(...args),
}));

vi.mock("/src/auto-reply/reply.js", () => ({
  getReplyFromConfig: (...args: Parameters<GetReplyFromConfigFn>) =>
    gatewayTestHoisted.getReplyFromConfig(...args),
}));
vi.mock("../auto-reply/reply/get-reply-from-config.runtime.js", () => ({
  getReplyFromConfig: (...args: Parameters<GetReplyFromConfigFn>) =>
    gatewayTestHoisted.getReplyFromConfig(...args),
}));
vi.mock("/src/auto-reply/reply/get-reply-from-config.runtime.js", () => ({
  getReplyFromConfig: (...args: Parameters<GetReplyFromConfigFn>) =>
    gatewayTestHoisted.getReplyFromConfig(...args),
}));
vi.mock("../cli/deps.js", async () => {
  const actual = await vi.importActual<typeof import("../cli/deps.js")>("../cli/deps.js");
  const base = actual.createDefaultDeps();
  return {
    ...actual,
    createDefaultDeps: () => ({
      ...base,
      sendMessageWhatsApp: (...args: unknown[]) =>
        (gatewayTestHoisted.sendWhatsAppMock as (...args: unknown[]) => unknown)(...args),
    }),
  };
});

vi.mock("../plugins/loader.js", async () => {
  const actual =
    await vi.importActual<typeof import("../plugins/loader.js")>("../plugins/loader.js");
  return {
    ...actual,
    loadOpenClawPlugins: () => getTestPluginRegistry(),
  };
});
vi.mock("../plugins/runtime/runtime-web-channel-plugin.js", () => ({
  sendWebChannelMessage: (...args: unknown[]) =>
    (gatewayTestHoisted.sendWhatsAppMock as (...args: unknown[]) => unknown)(...args),
}));
vi.mock("/src/plugins/runtime/runtime-web-channel-plugin.js", () => ({
  sendWebChannelMessage: (...args: unknown[]) =>
    (gatewayTestHoisted.sendWhatsAppMock as (...args: unknown[]) => unknown)(...args),
}));

process.env.OPENCLAW_SKIP_CHANNELS = "1";
process.env.OPENCLAW_SKIP_CRON = "1";
process.env.OPENCLAW_SKIP_CHANNELS = "1";
process.env.OPENCLAW_SKIP_CRON = "1";
