import { mkdir, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { AppConfig, GatewayStatus, RuntimeStatus } from "../../packages/shared-types/src/index.js";
import {
  ensureGatewayReadyForChat,
  isRecoverableGatewayChatError,
  runGatewayChatRequestWithRecovery,
  type ChatGatewayRecoveryCoordinator
} from "../../apps/desktop/src/main/chat-gateway-recovery.js";

function assert(condition: unknown, message: string): asserts condition {
  if (!condition) {
    throw new Error(message);
  }
}

function createConfig(overrides: Partial<AppConfig> = {}): AppConfig {
  return {
    setupMode: "employee-key",
    provider: "openai",
    baseUrl: "https://api.example.com",
    apiKeyConfigured: true,
    gatewayTokenConfigured: true,
    authTokenConfigured: true,
    defaultModel: "gpt-test",
    workspacePath: "D:/qjclaw/.tmp/workspace",
    gatewayUrl: "ws://127.0.0.1:18789",
    cloudApiBaseUrl: "https://cloud.example.com",
    runtimeCloudApiBaseUrl: "https://cloud.example.com",
    runtimeMode: "bundled-runtime",
    expertModelConfig: {
      image: {
        baseUrl: "",
        apiKeyConfigured: false,
        modelId: ""
      },
      video: {
        baseUrl: "",
        apiKeyConfigured: false,
        modelId: ""
      },
      copywriting: {
        baseUrl: "",
        apiKeyConfigured: false,
        modelId: ""
      },
      digitalHuman: {
        volcRegion: "cn-north-1",
        volcService: "cv",
        volcHost: "visual.volcengineapi.com",
        volcScheme: "https",
        ttsVoice: "zh-CN-YunxiNeural",
        qiniuBucket: "alketas",
        qiniuDomain: "http://tcwwu6wg4.hd-bkt.clouddn.com",
        qiniuKeyPrefix: "omnihuman",
        volcAccessKeyConfigured: false,
        volcSecretKeyConfigured: false,
        qiniuAccessKeyConfigured: false,
        qiniuSecretKeyConfigured: false
      }
    },
    douyinRuntimeConfig: {
      videoAnalyzer: {
        baseUrl: "",
        apiKeyConfigured: false,
        modelId: ""
      },
      replicationBrief: {
        baseUrl: "",
        apiKeyConfigured: false,
        modelId: ""
      },
      vectcut: {
        baseUrl: "",
        fileBaseUrl: "",
        apiKeyConfigured: false
      }
    },
    xhsFeishuConfig: {
      appIdConfigured: false,
      appSecretConfigured: false,
      appTokenConfigured: false,
      tableIdConfigured: false
    },
    ...overrides
  };
}

function createRuntimeStatus(overrides: Partial<RuntimeStatus> = {}): RuntimeStatus {
  return {
    requestedMode: "bundled-runtime",
    selectedMode: "bundled-runtime",
    activeMode: "bundled-runtime",
    payloadState: "ready",
    processState: "running",
    runtimeDir: "D:/runtime",
    nodeExecutable: "D:/runtime/node/node.exe",
    openClawEntry: "D:/runtime/openclaw/index.js",
    defaultConfigPath: "D:/runtime/config/openclaw.json",
    pythonExecutable: "D:/runtime/python/python.exe",
    pythonManifestPath: "D:/runtime/python/python-manifest.json",
    pythonReady: true,
    pythonVersion: "3.11.0",
    installedPythonPackages: [],
    pythonMissingModules: [],
    runtimeDataDir: "D:/runtime-data",
    runtimeStateDir: "D:/runtime-data/state",
    runtimeLogsDir: "D:/runtime-data/logs",
    logFilePath: "D:/runtime-data/logs/runtime-manager.log",
    gatewayUrl: "ws://127.0.0.1:18889",
    gatewayTokenConfigured: true,
    pid: 1234,
    startedAt: new Date().toISOString(),
    stoppedAt: undefined,
    lastExitCode: undefined,
    lastError: undefined,
    message: "Managed bundled runtime process is running and Gateway is ready.",
    modeReason: "Bundled runtime is selected.",
    detectedFiles: [],
    missingFiles: [],
    checkedAt: new Date().toISOString(),
    ...overrides
  };
}

function createGatewayStatus(overrides: Partial<GatewayStatus> = {}): GatewayStatus {
  return {
    state: "connected",
    url: "ws://127.0.0.1:18889",
    host: "127.0.0.1",
    port: 18889,
    version: "test",
    transport: "websocket",
    lastConnectedAt: new Date().toISOString(),
    lastError: undefined,
    availableMethods: [],
    message: "Gateway connected.",
    ...overrides
  };
}

function createCoordinatorFixture(input: {
  config?: AppConfig;
  runtimeStatus?: RuntimeStatus;
  gatewayStatus?: GatewayStatus | null;
  shouldRefreshGatewayClient?: boolean[];
  onStartManagedRuntime?: () => RuntimeStatus;
}): ChatGatewayRecoveryCoordinator & {
  counters: {
    startManagedRuntime: number;
    reconfigureGatewayClient: number;
    connectGatewayClientWithRetry: number;
    shouldRefreshGatewayClient: number;
  };
} {
  const config = input.config ?? createConfig();
  let runtimeStatus = input.runtimeStatus ?? createRuntimeStatus();
  let gatewayStatus = input.gatewayStatus ?? createGatewayStatus();
  const refreshDecisions = [...(input.shouldRefreshGatewayClient ?? [false])];
  const counters = {
    startManagedRuntime: 0,
    reconfigureGatewayClient: 0,
    connectGatewayClientWithRetry: 0,
    shouldRefreshGatewayClient: 0
  };

  return {
    counters,
    getConfig: async () => config,
    runtimeStatus: async () => runtimeStatus,
    gatewayStatus: async () => gatewayStatus,
    startManagedRuntime: async () => {
      counters.startManagedRuntime += 1;
      runtimeStatus = input.onStartManagedRuntime?.() ?? createRuntimeStatus();
      return runtimeStatus;
    },
    shouldRefreshGatewayClient: async () => {
      counters.shouldRefreshGatewayClient += 1;
      return refreshDecisions.shift() ?? false;
    },
    reconfigureGatewayClient: async () => {
      counters.reconfigureGatewayClient += 1;
      gatewayStatus = createGatewayStatus({
        state: "disconnected",
        message: "Gateway configuration updated."
      });
    },
    connectGatewayClientWithRetry: async () => {
      counters.connectGatewayClientWithRetry += 1;
      gatewayStatus = createGatewayStatus();
    }
  };
}

async function main(): Promise<void> {
  const __filename = fileURLToPath(import.meta.url);
  const __dirname = path.dirname(__filename);
  const repoRoot = path.resolve(__dirname, "..", "..");
  const resultPath = path.resolve(process.argv[2] ?? path.join(repoRoot, ".tmp", "chat-gateway-recovery-smoke", "result.json"));
  const tempRoot = path.dirname(resultPath);

  await rm(tempRoot, { recursive: true, force: true });
  await mkdir(tempRoot, { recursive: true });

  assert(isRecoverableGatewayChatError("Gateway websocket is not open."), "Expected websocket-not-open to be recoverable.");
  assert(isRecoverableGatewayChatError("Gateway closed during connect (1006)."), "Expected 1006 to be recoverable.");
  assert(!isRecoverableGatewayChatError("Policy violation"), "Policy violation must not be treated as recoverable chat transport noise.");

  const readinessCoordinator = createCoordinatorFixture({
    runtimeStatus: createRuntimeStatus({
      processState: "starting",
      message: "Managed bundled runtime is starting."
    }),
    gatewayStatus: createGatewayStatus({
      state: "disconnected",
      message: "Gateway disconnected."
    }),
    shouldRefreshGatewayClient: [true]
  });

  await ensureGatewayReadyForChat(readinessCoordinator, "chat-send");
  assert(readinessCoordinator.counters.startManagedRuntime === 1, "Bundled runtime chat preparation should start the managed runtime when it is not running.");
  assert(readinessCoordinator.counters.reconfigureGatewayClient === 1, "Bundled runtime chat preparation should reconfigure the gateway client when refresh is required.");
  assert(readinessCoordinator.counters.connectGatewayClientWithRetry === 1, "Bundled runtime chat preparation should reconnect the gateway client when disconnected.");

  const retryCoordinator = createCoordinatorFixture({
    shouldRefreshGatewayClient: [false]
  });
  let executeCount = 0;
  const retryResult = await runGatewayChatRequestWithRecovery(retryCoordinator, {
    reason: "chat-stream",
    execute: async () => {
      executeCount += 1;
      if (executeCount === 1) {
        throw new Error("Gateway websocket is not open.");
      }
      return {
        requestId: "request-1",
        sessionId: "session-1"
      };
    }
  });

  assert(executeCount === 2, "Recoverable chat failures should retry the gateway request exactly once.");
  assert(retryCoordinator.counters.reconfigureGatewayClient === 1, "Recoverable chat failures should force a gateway reconfigure before retry.");
  assert(retryCoordinator.counters.connectGatewayClientWithRetry === 1, "Recoverable chat failures should reconnect the gateway before retry.");

  const failureCoordinator = createCoordinatorFixture({
    shouldRefreshGatewayClient: [false]
  });
  let unrecoverableError = "";
  try {
    await runGatewayChatRequestWithRecovery(failureCoordinator, {
      reason: "chat-send",
      execute: async () => {
        throw new Error("Policy violation");
      }
    });
  } catch (error) {
    unrecoverableError = error instanceof Error ? error.message : String(error);
  }

  assert(unrecoverableError === "Policy violation", "Unrecoverable gateway errors should surface unchanged.");
  assert(failureCoordinator.counters.reconfigureGatewayClient === 0, "Unrecoverable gateway errors must not trigger forced reconnect.");
  assert(failureCoordinator.counters.connectGatewayClientWithRetry === 0, "Unrecoverable gateway errors must not trigger forced reconnect.");

  const summary = {
    ok: true,
    readinessCounters: readinessCoordinator.counters,
    retryCounters: retryCoordinator.counters,
    retryResult,
    executeCount,
    unrecoverableError
  };

  await writeFile(resultPath, JSON.stringify(summary, null, 2), "utf8");
  console.log(JSON.stringify(summary, null, 2));
}

main().catch(async (error) => {
  const __filename = fileURLToPath(import.meta.url);
  const __dirname = path.dirname(__filename);
  const repoRoot = path.resolve(__dirname, "..", "..");
  const resultPath = path.resolve(process.argv[2] ?? path.join(repoRoot, ".tmp", "chat-gateway-recovery-smoke", "result.json"));
  const failure = {
    ok: false,
    error: error instanceof Error ? error.stack ?? error.message : String(error)
  };

  await mkdir(path.dirname(resultPath), { recursive: true });
  await writeFile(resultPath, JSON.stringify(failure, null, 2), "utf8");
  console.error(failure.error);
  process.exitCode = 1;
});
