import { describe, expect, it, vi } from "vitest";

const createQaScenarioRuntimeApi = vi.hoisted(() => vi.fn());
const waitForOutboundMessage = vi.hoisted(() => vi.fn());
const waitForTransportOutboundMessage = vi.hoisted(() => vi.fn());
const waitForChannelOutboundMessage = vi.hoisted(() => vi.fn());
const waitForNoOutbound = vi.hoisted(() => vi.fn());
const waitForNoTransportOutbound = vi.hoisted(() => vi.fn());
const recentOutboundSummary = vi.hoisted(() => vi.fn());
const formatConversationTranscript = vi.hoisted(() => vi.fn());
const readTransportTranscript = vi.hoisted(() => vi.fn());
const formatTransportTranscript = vi.hoisted(() => vi.fn());
const fetchJson = vi.hoisted(() => vi.fn());
const waitForGatewayHealthy = vi.hoisted(() => vi.fn());
const waitForTransportReady = vi.hoisted(() => vi.fn());
const waitForQaChannelReady = vi.hoisted(() => vi.fn());
const patchConfig = vi.hoisted(() => vi.fn());
const applyConfig = vi.hoisted(() => vi.fn());
const readConfigSnapshot = vi.hoisted(() => vi.fn());
const waitForConfigRestartSettle = vi.hoisted(() => vi.fn());
const createSession = vi.hoisted(() => vi.fn());
const readEffectiveTools = vi.hoisted(() => vi.fn());
const readSkillStatus = vi.hoisted(() => vi.fn());
const readRawQaSessionStore = vi.hoisted(() => vi.fn());
const runQaCli = vi.hoisted(() => vi.fn());
const extractMediaPathFromText = vi.hoisted(() => vi.fn());
const resolveGeneratedImagePath = vi.hoisted(() => vi.fn());
const startAgentRun = vi.hoisted(() => vi.fn());
const waitForAgentRun = vi.hoisted(() => vi.fn());
const listCronJobs = vi.hoisted(() => vi.fn());
const waitForCronRunCompletion = vi.hoisted(() => vi.fn());
const readDoctorMemoryStatus = vi.hoisted(() => vi.fn());
const forceMemoryIndex = vi.hoisted(() => vi.fn());
const findSkill = vi.hoisted(() => vi.fn());
const writeWorkspaceSkill = vi.hoisted(() => vi.fn());
const callPluginToolsMcp = vi.hoisted(() => vi.fn());
const runAgentPrompt = vi.hoisted(() => vi.fn());
const ensureImageGenerationConfigured = vi.hoisted(() => vi.fn());
const handleQaAction = vi.hoisted(() => vi.fn());
const extractQaToolPayload = vi.hoisted(() => vi.fn());
const browserRequest = vi.hoisted(() => vi.fn());
const waitForBrowserReady = vi.hoisted(() => vi.fn());
const browserOpenTab = vi.hoisted(() => vi.fn());
const browserSnapshot = vi.hoisted(() => vi.fn());
const browserAct = vi.hoisted(() => vi.fn());
const webOpenPage = vi.hoisted(() => vi.fn(async () => ({ pageId: "page-1" })));
const webWait = vi.hoisted(() => vi.fn());
const webType = vi.hoisted(() => vi.fn());
const webSnapshot = vi.hoisted(() => vi.fn());
const webEvaluate = vi.hoisted(() => vi.fn());
const hasDiscoveryLabels = vi.hoisted(() => vi.fn());
const reportsDiscoveryScopeLeak = vi.hoisted(() => vi.fn());
const reportsMissingDiscoveryFiles = vi.hoisted(() => vi.fn());
const hasModelSwitchContinuityEvidence = vi.hoisted(() => vi.fn());
const qaChannelPlugin = vi.hoisted(() => ({ id: "qa-channel" }));

vi.mock("./scenario-runtime-api.js", () => ({
  createQaScenarioRuntimeApi,
}));

vi.mock("./suite-runtime-transport.js", () => ({
  waitForOutboundMessage,
  waitForTransportOutboundMessage,
  waitForChannelOutboundMessage,
  waitForNoOutbound,
  waitForNoTransportOutbound,
  recentOutboundSummary,
  formatConversationTranscript,
  readTransportTranscript,
  formatTransportTranscript,
}));

vi.mock("./suite-runtime-gateway.js", () => ({
  fetchJson,
  waitForGatewayHealthy,
  waitForTransportReady,
  waitForQaChannelReady,
  waitForConfigRestartSettle,
  patchConfig,
  applyConfig,
  readConfigSnapshot,
}));

vi.mock("./suite-runtime-agent.js", () => ({
  createSession,
  readEffectiveTools,
  readSkillStatus,
  readRawQaSessionStore,
  runQaCli,
  extractMediaPathFromText,
  resolveGeneratedImagePath,
  startAgentRun,
  waitForAgentRun,
  listCronJobs,
  readDoctorMemoryStatus,
  forceMemoryIndex,
  findSkill,
  writeWorkspaceSkill,
  callPluginToolsMcp,
  runAgentPrompt,
  ensureImageGenerationConfigured,
  handleQaAction,
}));

vi.mock("./browser-runtime.js", () => ({
  callQaBrowserRequest: browserRequest,
  waitForQaBrowserReady: waitForBrowserReady,
  qaBrowserOpenTab: browserOpenTab,
  qaBrowserSnapshot: browserSnapshot,
  qaBrowserAct: browserAct,
}));

vi.mock("./web-runtime.js", () => ({
  qaWebOpenPage: webOpenPage,
  qaWebWait: webWait,
  qaWebType: webType,
  qaWebSnapshot: webSnapshot,
  qaWebEvaluate: webEvaluate,
}));

vi.mock("./cron-run-wait.js", () => ({
  waitForCronRunCompletion,
}));

vi.mock("./discovery-eval.js", () => ({
  hasDiscoveryLabels,
  reportsDiscoveryScopeLeak,
  reportsMissingDiscoveryFiles,
}));

vi.mock("./extract-tool-payload.js", () => ({
  extractQaToolPayload,
}));

vi.mock("./model-switch-eval.js", () => ({
  hasModelSwitchContinuityEvidence,
}));

vi.mock("./runtime-api.js", () => ({
  qaChannelPlugin,
}));

import { createQaSuiteScenarioFlowApi } from "./suite-runtime-flow.js";
import type { QaSuiteRuntimeEnv } from "./suite-runtime-types.js";

describe("qa suite runtime flow", () => {
  it("wires the split suite runtime deps into the scenario runtime api", async () => {
    const env = {
      lab: { baseUrl: "http://127.0.0.1:4444" },
      webSessionIds: new Set<string>(),
      gateway: {} as QaSuiteRuntimeEnv["gateway"],
      transport: {
        id: "qa-channel",
        label: "QA Channel",
        accountId: "qa-channel",
        waitReady: vi.fn(),
        createGatewayConfig: vi.fn(),
        buildAgentDelivery: vi.fn(),
        requiredPluginIds: [],
        handleAction: vi.fn(),
        createReportNotes: vi.fn(),
        state: {
          reset: vi.fn(),
          getSnapshot: vi.fn(),
          addInboundMessage: vi.fn(),
          addOutboundMessage: vi.fn(),
          readMessage: vi.fn(),
          searchMessages: vi.fn(),
          waitFor: vi.fn(),
        },
        capabilities: {
          waitForOutboundMessage: vi.fn(),
          waitForCondition: vi.fn(),
          getNormalizedMessageState: vi.fn(),
          resetNormalizedMessageState: vi.fn(),
          sendInboundMessage: vi.fn(),
          injectOutboundMessage: vi.fn(),
          readNormalizedMessage: vi.fn(),
          executeGenericAction: vi.fn(),
          waitForReady: vi.fn(),
          assertNoFailureReplies: vi.fn(),
        },
      },
      repoRoot: "/repo",
      providerMode: "mock-openai",
      primaryModel: "openai/gpt-5.4",
      alternateModel: "openai/gpt-5.4-mini",
      mock: null,
      cfg: {} as QaSuiteRuntimeEnv["cfg"],
    } satisfies Parameters<typeof createQaSuiteScenarioFlowApi>[0]["env"];
    const scenario = {
      id: "session-memory-ranking",
      title: "Session memory ranking",
      sourcePath: "qa/scenarios/session-memory-ranking.md",
      surface: "qa-channel",
      objective: "test",
      successCriteria: ["test"],
      execution: {
        kind: "flow" as const,
        config: { expected: "value" },
        flow: { steps: [] },
      },
    };
    const runScenario = vi.fn();
    const splitModelRef = vi.fn();
    const formatErrorMessage = vi.fn();
    const liveTurnTimeoutMs = vi.fn();
    const resolveQaLiveTurnTimeoutMs = vi.fn();
    createQaScenarioRuntimeApi.mockReturnValue({ api: "ok" });

    const result = createQaSuiteScenarioFlowApi({
      env,
      scenario,
      runScenario,
      splitModelRef,
      formatErrorMessage,
      liveTurnTimeoutMs,
      resolveQaLiveTurnTimeoutMs,
      constants: {
        imageUnderstandingPngBase64: "small",
        imageUnderstandingLargePngBase64: "large",
        imageUnderstandingValidPngBase64: "valid",
      },
    });

    expect(result).toEqual({ api: "ok" });
    expect(createQaScenarioRuntimeApi).toHaveBeenCalledTimes(1);
    const call = createQaScenarioRuntimeApi.mock.calls[0]?.[0] as {
      env: typeof env;
      scenario: typeof scenario;
      deps: {
        runScenario: typeof runScenario;
        waitForQaChannelReady: typeof waitForQaChannelReady;
        waitForOutboundMessage: typeof waitForOutboundMessage;
        forceMemoryIndex: typeof forceMemoryIndex;
        runAgentPrompt: typeof runAgentPrompt;
        qaChannelPlugin: typeof qaChannelPlugin;
        webOpenPage: (params: { url: string }) => Promise<unknown>;
      };
      constants: {
        imageUnderstandingPngBase64: string;
        imageUnderstandingLargePngBase64: string;
        imageUnderstandingValidPngBase64: string;
      };
    };
    expect(call.env).toBe(env);
    expect(call.scenario).toBe(scenario);
    expect(call.deps.runScenario).toBe(runScenario);
    expect(call.deps.waitForQaChannelReady).toBe(waitForQaChannelReady);
    expect(call.deps.waitForOutboundMessage).toBe(waitForOutboundMessage);
    expect(call.deps.forceMemoryIndex).toBe(forceMemoryIndex);
    expect(call.deps.runAgentPrompt).toBe(runAgentPrompt);
    expect(call.deps.qaChannelPlugin).toBe(qaChannelPlugin);
    expect(call.constants).toEqual({
      imageUnderstandingPngBase64: "small",
      imageUnderstandingLargePngBase64: "large",
      imageUnderstandingValidPngBase64: "valid",
    });

    await call.deps.webOpenPage({ url: "https://openclaw.ai" });
    expect(webOpenPage).toHaveBeenCalledWith({ url: "https://openclaw.ai" });
    expect(env.webSessionIds.has("page-1")).toBe(true);
  });
});
