import { randomUUID } from "node:crypto";
import { setTimeout as sleep } from "node:timers/promises";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { startQaGatewayChild } from "./gateway-child.js";
import { startQaLabServer } from "./lab-server.js";
import { resolveQaLiveTurnTimeoutMs } from "./live-timeout.js";
import type { QaProviderMode } from "./model-selection.js";
import { startQaProviderServer } from "./providers/server-runtime.js";
import type { QaThinkingLevel } from "./qa-gateway-config.js";
import { createQaTransportAdapter, type QaTransportId } from "./qa-transport-registry.js";

type QaManualLaneParams = {
  repoRoot: string;
  transportId?: QaTransportId;
  providerMode: QaProviderMode;
  primaryModel: string;
  alternateModel: string;
  fastMode?: boolean;
  thinkingDefault?: QaThinkingLevel;
  message: string;
  timeoutMs?: number;
  replySettleMs?: number;
};

function resolveManualLaneTimeoutMs(params: {
  providerMode: QaProviderMode;
  primaryModel: string;
  alternateModel: string;
  timeoutMs?: number;
}) {
  if (
    typeof params.timeoutMs === "number" &&
    Number.isFinite(params.timeoutMs) &&
    params.timeoutMs > 0
  ) {
    return params.timeoutMs;
  }
  return resolveQaLiveTurnTimeoutMs(
    {
      providerMode: params.providerMode,
      primaryModel: params.primaryModel,
      alternateModel: params.alternateModel,
    },
    120_000,
    params.primaryModel,
  );
}

export async function runQaManualLane(params: QaManualLaneParams) {
  const sessionSuffix = params.primaryModel.replace(/[^a-z0-9._-]+/gi, "-");
  const lab = await startQaLabServer({
    repoRoot: params.repoRoot,
    embeddedGateway: "disabled",
  });
  const transport = createQaTransportAdapter({
    id: params.transportId ?? "qa-channel",
    state: lab.state,
  });
  const mock = await startQaProviderServer(params.providerMode);
  const gateway = await startQaGatewayChild({
    repoRoot: params.repoRoot,
    providerBaseUrl: mock ? `${mock.baseUrl}/v1` : undefined,
    transport,
    transportBaseUrl: lab.listenUrl,
    providerMode: params.providerMode,
    primaryModel: params.primaryModel,
    alternateModel: params.alternateModel,
    fastMode: params.fastMode,
    thinkingDefault: params.thinkingDefault,
    controlUiEnabled: false,
  });

  const timeoutMs = resolveManualLaneTimeoutMs({
    providerMode: params.providerMode,
    primaryModel: params.primaryModel,
    alternateModel: params.alternateModel,
    timeoutMs: params.timeoutMs,
  });
  try {
    const delivery = transport.buildAgentDelivery({
      target: "dm:qa-operator",
    });
    const started = (await gateway.call(
      "agent",
      {
        idempotencyKey: randomUUID(),
        agentId: "qa",
        sessionKey: `agent:qa:manual:${sessionSuffix}`,
        message: params.message,
        deliver: true,
        channel: delivery.channel,
        to: "dm:qa-operator",
        replyChannel: delivery.replyChannel,
        replyTo: delivery.replyTo,
      },
      { timeoutMs: 30_000 },
    )) as { runId?: string };

    if (!started.runId) {
      throw new Error(`agent call did not return a runId: ${JSON.stringify(started)}`);
    }

    const waited = (await gateway.call(
      "agent.wait",
      {
        runId: started.runId,
        timeoutMs,
      },
      { timeoutMs: timeoutMs + 5_000 },
    )) as { status?: string; error?: string };

    const replySettleMs = params.replySettleMs ?? 500;
    if (replySettleMs > 0) {
      await sleep(replySettleMs);
    }

    const reply =
      lab.state
        .getSnapshot()
        .messages.findLast(
          (candidate) =>
            candidate.direction === "outbound" && candidate.conversation.id === "qa-operator",
        )?.text ?? null;

    return {
      model: params.primaryModel,
      waited,
      reply,
      watchUrl: lab.baseUrl,
    };
  } catch (error) {
    throw new Error(formatErrorMessage(error), { cause: error });
  } finally {
    await gateway.stop();
    await mock?.stop();
    await lab.stop();
  }
}
