import { mkdir, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { AppConfig, GatewayStatus, RuntimeCloudStatus, RuntimeStatus } from "../../packages/shared-types/src/index.js";
import {
  resolveEffectiveGatewayToken,
  resolveEffectiveGatewayUrl,
  shouldUseLocalOpenClawGateway
} from "../../apps/desktop/src/main/services/openclaw-local-config.js";
import {
  buildChatSummary,
  isWorkspaceShellReady,
  isBundledRuntimeNameConflictError,
  isGatewayPolicyViolationError,
  isTransientLocalGatewayError,
  requiresShellWarmupBeforeBinding,
  shouldRetryBootstrapWarmup,
  shouldRetryManagedRuntimeStartup,
  toStartupErrorMessage
} from "../../apps/desktop/src/main/workspace-startup.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
      },
      video: {
        baseUrl: "",
        apiKeyConfigured: false
      },
      copywriting: {
        baseUrl: "",
        apiKeyConfigured: false
      },
      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 createRuntimeCloudStatus(overrides: Partial<RuntimeCloudStatus> = {}): RuntimeCloudStatus {
  return {
    state: "ready",
    baseUrl: "https://cloud.example.com",
    apiKeyConfigured: true,
    lastFetchedAt: new Date().toISOString(),
    lastError: undefined,
    config: undefined,
    ...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
  };
}

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", "workspace-startup-smoke", "result.json"));
  const tempRoot = path.dirname(resultPath);

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

  const transientCodes = [
    "Gateway websocket is not open.",
    "Gateway closed during connect (1006).",
    "Gateway closed during connect (1005).",
    "Gateway closed during connect (1000).",
    "Gateway closed before health probe completed (1000).",
    "Timed out while probing reusable Gateway status and health at ws://127.0.0.1:18889.",
    "Timed out while probing bundled Gateway status and health at ws://127.0.0.1:18889.",
    "Timed out while connecting to ws://127.0.0.1:18889.",
    "Gateway health probe reported not ok.",
    "gateway starting; retry shortly | startup-sidecars | UNAVAILABLE",
    "connect ECONNREFUSED 127.0.0.1:18889"
  ];
  for (const message of transientCodes) {
    assert(isTransientLocalGatewayError(message), `Expected transient startup classification for: ${message}`);
  }
  assert(isGatewayPolicyViolationError("Gateway connection closed (1008)."), "Expected 1008 to be classified as gateway policy violation.");
  assert(isBundledRuntimeNameConflictError("gateway name/hostname conflict detected via bonjour"), "Expected bonjour name conflict to be classified correctly.");
  const policyViolationMessage = toStartupErrorMessage("Gateway connection closed (1008).", "fallback");
  assert(!policyViolationMessage.includes("本机已有 OpenClaw 网关正在运行"), "Policy violation message should no longer hard-assert a local OpenClaw gateway conflict.");
  const macSecurityMessage = toStartupErrorMessage("macSecurityBlock: macOS blocked bundled runtime execution (Operation not permitted).", "fallback");
  assert(macSecurityMessage.includes("macOS 阻止了内置运行时启动"), "mac security blocks should get a specific startup diagnostic.");

  const config = createConfig();
  const runtimeStatus = createRuntimeStatus({
    processState: "error",
    lastError: "Gateway closed during connect (1006)."
  });
  const gatewayStatus = createGatewayStatus({
    state: "error",
    lastError: "Gateway closed during connect (1006).",
    message: "Gateway closed during connect (1006)."
  });

  const startupSummary = buildChatSummary({
    config,
    runtimeStatus,
    runtimeCloudStatus: createRuntimeCloudStatus(),
    gatewayStatus,
    warmupInFlight: true,
    runtimeCloudRefreshInFlight: false,
    runtimeCloudConfigSyncInFlight: false,
    isPackaged: true
  });
  assert(startupSummary.chatLaunchState === "starting", "Transient packaged startup failures should remain in starting state.");
  assert(startupSummary.startupPhase === "starting-runtime", "Transient runtime failures should map to starting-runtime.");
  assert(startupSummary.startupMessage === "本地助手正在启动，请稍候。", "Transient runtime startup should use the unified local assistant starting message.");

  const gatewayOnlySummary = buildChatSummary({
    config,
    runtimeStatus: createRuntimeStatus(),
    runtimeCloudStatus: createRuntimeCloudStatus(),
    gatewayStatus,
    warmupInFlight: true,
    runtimeCloudRefreshInFlight: false,
    runtimeCloudConfigSyncInFlight: false,
    isPackaged: true
  });
  assert(gatewayOnlySummary.chatLaunchState === "starting", "Transient gateway failures should remain in starting state during packaged warmup.");
  assert(gatewayOnlySummary.startupPhase === "connecting-gateway", "Transient gateway failures should map to connecting-gateway.");
  assert(gatewayOnlySummary.startupMessage === "本地助手正在启动，请稍候。", "Transient gateway startup should use the unified local assistant starting message.");

  assert(shouldRetryBootstrapWarmup({
    config,
    runtimeStatus,
    gatewayStatus,
    isPackaged: true
  }), "Packaged bundled-runtime bootstrap should retry transient startup failures.");
  assert(shouldRetryManagedRuntimeStartup(config, runtimeStatus), "Packaged bundled-runtime startup should retry transient runtime failures.");

  const syncingConfigSummary = buildChatSummary({
    config,
    runtimeStatus: createRuntimeStatus({
      processState: "running",
      activeMode: "bundled-runtime"
    }),
    runtimeCloudStatus: createRuntimeCloudStatus(),
    gatewayStatus: createGatewayStatus({
      state: "connected"
    }),
    warmupInFlight: false,
    runtimeCloudRefreshInFlight: true,
    runtimeCloudConfigSyncInFlight: false,
    isPackaged: false
  });
  assert(syncingConfigSummary.chatLaunchState === "starting", "Runtime cloud refresh should keep chat unavailable while config is syncing.");
  assert(syncingConfigSummary.startupPhase === "syncing-config", "Runtime cloud refresh should map to syncing-config.");

  const policyViolationRuntimeStatus = createRuntimeStatus({
    processState: "error",
    lastError: "Gateway connection closed (1008)."
  });
  const policyViolationGatewayStatus = createGatewayStatus({
    state: "error",
    lastError: "Gateway connection closed (1008).",
    message: "Gateway connection closed (1008)."
  });
  assert(shouldRetryBootstrapWarmup({
    config,
    runtimeStatus: createRuntimeStatus(),
    gatewayStatus: policyViolationGatewayStatus,
    isPackaged: true
  }) === false, "Packaged bundled-runtime bootstrap should not retry gateway policy violations.");
  assert(!shouldRetryManagedRuntimeStartup(config, policyViolationRuntimeStatus), "Packaged bundled-runtime startup should not retry runtime policy violations.");

  const nameConflictGatewayStatus = createGatewayStatus({
    state: "error",
    lastError: "Gateway name/hostname conflict detected via bonjour.",
    message: "Gateway name/hostname conflict detected via bonjour."
  });
  assert(shouldRetryBootstrapWarmup({
    config,
    runtimeStatus: createRuntimeStatus(),
    gatewayStatus: nameConflictGatewayStatus,
    isPackaged: true
  }) === false, "Packaged bundled-runtime bootstrap should not retry bundled runtime name conflicts.");

  const unboundConfig = createConfig({
    apiKeyConfigured: false
  });
  assert(requiresShellWarmupBeforeBinding(unboundConfig), "Unbound employee-key config should require shell warmup before binding.");
  assert(!isWorkspaceShellReady({
    config: unboundConfig,
    runtimeStatus: createRuntimeStatus({
      processState: "starting",
      activeMode: "bundled-runtime",
      message: "Starting managed bundled runtime process."
    }),
    gatewayStatus: createGatewayStatus({
      state: "connecting",
      message: "Gateway is connecting."
    })
  }), "Shell should remain unready while bundled runtime/gateway warmup is incomplete.");
  assert(isWorkspaceShellReady({
    config: unboundConfig,
    runtimeStatus: createRuntimeStatus({
      processState: "running",
      activeMode: "bundled-runtime"
    }),
    gatewayStatus: createGatewayStatus({
      state: "connected"
    })
  }), "Shell should become ready only after bundled runtime and gateway are both ready.");
  const unboundStartingSummary = buildChatSummary({
    config: unboundConfig,
    runtimeStatus: createRuntimeStatus({
      processState: "starting",
      activeMode: "bundled-runtime",
      message: "Starting managed bundled runtime process."
    }),
    runtimeCloudStatus: createRuntimeCloudStatus({
      state: "unconfigured",
      apiKeyConfigured: false
    }),
    gatewayStatus: createGatewayStatus({
      state: "connecting",
      message: "Gateway is connecting."
    }),
    warmupInFlight: true,
    runtimeCloudRefreshInFlight: false,
    runtimeCloudConfigSyncInFlight: false,
    isPackaged: true
  });
  assert(unboundStartingSummary.chatLaunchState === "starting", "Unbound packaged startup should remain in starting before shell prewarm completes.");
  assert(unboundStartingSummary.startupPhase === "starting-runtime", "Unbound packaged startup should report starting-runtime before binding.");
  const unboundReadyForBindSummary = buildChatSummary({
    config: unboundConfig,
    runtimeStatus: createRuntimeStatus({
      processState: "running",
      activeMode: "bundled-runtime"
    }),
    runtimeCloudStatus: createRuntimeCloudStatus({
      state: "unconfigured",
      apiKeyConfigured: false
    }),
    gatewayStatus: createGatewayStatus({
      state: "connected"
    }),
    warmupInFlight: false,
    runtimeCloudRefreshInFlight: false,
    runtimeCloudConfigSyncInFlight: false,
    isPackaged: true
  });
  assert(unboundReadyForBindSummary.chatLaunchState === "unbound", "Unbound packaged startup should expose binding only after shell prewarm completes.");

  assert(!shouldUseLocalOpenClawGateway(true, "bundled-runtime"), "Packaged bundled-runtime mode should ignore local OpenClaw.");
  assert(shouldUseLocalOpenClawGateway(true, "external-gateway"), "Packaged external-gateway mode should allow local OpenClaw.");
  assert(shouldUseLocalOpenClawGateway(false, "bundled-runtime"), "Dev mode should allow local OpenClaw.");

  const ignoredGatewayUrl = resolveEffectiveGatewayUrl("ws://127.0.0.1:18789", undefined);
  assert(ignoredGatewayUrl === "ws://127.0.0.1:18789", "Configured gateway URL should remain unchanged when no local override is used.");
  const discoveredGatewayUrl = resolveEffectiveGatewayUrl("ws://127.0.0.1:18789", "ws://127.0.0.1:29999");
  assert(discoveredGatewayUrl === "ws://127.0.0.1:29999", "Default gateway URL should yield to the discovered local OpenClaw URL when allowed.");
  const resolvedGatewayToken = resolveEffectiveGatewayToken("", "local-token");
  assert(resolvedGatewayToken === "local-token", "Local OpenClaw token should be used only when no configured token exists.");

  const summary = {
    ok: true,
    transientCodes,
    startupSummary,
    gatewayOnlySummary,
    shouldRetryBootstrap: true,
    shouldRetryManagedRuntime: true,
    policyViolationMessage,
    localOpenClawPolicy: {
      packagedBundledRuntime: shouldUseLocalOpenClawGateway(true, "bundled-runtime"),
      packagedExternalGateway: shouldUseLocalOpenClawGateway(true, "external-gateway"),
      devBundledRuntime: shouldUseLocalOpenClawGateway(false, "bundled-runtime")
    },
    resolvedGatewayUrl: discoveredGatewayUrl,
    resolvedGatewayToken
  };

  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", "workspace-startup-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;
});
