import { existsSync } from "node:fs";
import { isAbsolute, join } from "node:path";

import { rootPath } from "../config";
import { runCommand } from "../command";
import {
  asRecord,
  integerField,
  numberField,
  readYamlRecord,
  recordField,
  stringArrayField,
  stringField,
} from "../platform-infra-ops-library";
import { readEnvSourceFile, requiredEnvValue } from "../secrets";

type NativeAction = "start" | "stop" | "restart" | "status" | "logs";

interface AgentRunNativeManagerSpec {
  workspace: string;
  bindHost: string;
  probeHost: string;
  port: number;
  store: "memory";
  stateDir: string;
  apiKeySourceRef: string;
  apiKeySourceKey: string;
  kubeconfig: string;
  kubectlCommand: string;
  runtimeNamespace: string;
  lane: string;
  callbackBaseUrl: string;
  unideskSshEndpointEnv: { name: string; value: string };
  runner: {
    image: string;
    bootRepoUrl: string;
    serviceAccount: string;
    jobNamePrefix: string;
    nodeSelector: Record<string, string>;
    apiKeySecretRef: { name: string; key: string };
    idleTimeoutMs: number;
    backendRetry: { maxAttempts: number; initialBackoffMs: number; maxBackoffMs: number };
    egressProxyUrl: string;
    noProxyExtra: string[];
  };
  dispatcher: {
    intervalMs: number;
    batchSize: number;
    leaseMs: number;
    attemptTimeoutMs: number;
    maxAttempts: number;
    retryBackoffMs: number;
  };
  reconciler: { intervalMs: number; batchSize: number };
  startupRetry: {
    maxAttempts: number;
    initialBackoffMs: number;
    maxBackoffMs: number;
    multiplier: number;
    totalDeadlineMs: number;
    jitterRatio: number;
  };
  kafka: {
    bootstrapServers: string;
    dnsServers: string[];
    dnsSearchDomains: string[];
    eventTopic: string;
    clientId: string;
    stdioTopic: string;
    stdioClientId: string;
    outboxIntervalMs: number;
    outboxBatchSize: number;
    outboxLeaseMs: number;
    outboxRetryBackoffMs: number;
  };
  publicExposure: {
    publicBaseUrl: string;
    hostname: string;
    expectedA: string;
    upstream: string;
  };
}

const CONFIG_PATH = "config/agentrun.yaml";

export function agentRunNativeDevelopmentHelp(): Record<string, unknown> {
  return {
    ok: true,
    command: "agentrun native-development manager",
    usage: [
      "bun scripts/cli.ts agentrun native-development manager start",
      "bun scripts/cli.ts agentrun native-development manager stop|restart|status|logs",
    ],
    contract: "AgentRun L1 manager 由 owning YAML 独立启动，使用独立 store/job 前缀，并通过固定 HTTPS 域名供 runner 回调。",
    configSource: `${CONFIG_PATH}#nativeDevelopment.manager`,
  };
}

export function runAgentRunNativeDevelopmentCommand(args: string[]): Record<string, unknown> {
  if (args.length === 0 || args.some(isHelpArg)) return agentRunNativeDevelopmentHelp();
  if (args[0] !== "manager") throw new Error("agentrun native-development service must be manager");
  const action = nativeAction(args[1]);
  const spec = readNativeManagerSpec();
  if (action === "restart") {
    const stopped = runManagerAction(spec, "stop");
    if (stopped.ok === false) return { ...stopped, action: "restart", restartStage: "stop" };
    const started = runManagerAction(spec, "start");
    return { ...started, action: "restart", stopResult: stopped.serviceResult };
  }
  return runManagerAction(spec, action);
}

function runManagerAction(spec: AgentRunNativeManagerSpec, action: Exclude<NativeAction, "restart">): Record<string, unknown> {
  validateLocalInputs(spec);
  const command = [
    "bun",
    "scripts/agentrun-cli.ts",
    "server",
    action,
    "--port",
    String(spec.port),
    ...(action === "start" ? ["--host", spec.bindHost, "--store", spec.store] : []),
  ];
  const result = runCommand(command, spec.workspace, {
    timeoutMs: 15_000,
    env: { ...process.env, ...nativeEnvironment(spec) },
  });
  const payload = parsePayload(result.stdout);
  const serviceResult = payload.data && typeof payload.data === "object" && !Array.isArray(payload.data)
    ? payload.data as Record<string, unknown>
    : payload;
  const serviceOk = result.exitCode === 0 && payload.ok !== false;
  return {
    ok: serviceOk,
    command: `agentrun native-development manager ${action}`,
    service: "manager",
    action,
    configSource: `${CONFIG_PATH}#nativeDevelopment.manager`,
    userEndpoint: { url: spec.publicExposure.publicBaseUrl },
    debugEndpoint: { url: spec.callbackBaseUrl },
    endpoints: {
      bind: { host: spec.bindHost, port: spec.port },
      probe: { host: spec.probeHost, port: spec.port, url: `http://${spec.probeHost}:${spec.port}/health/readiness` },
      public: { url: spec.publicExposure.publicBaseUrl },
    },
    serviceResult,
    warnings: [{
      code: "agentrun-native-public-edge-reconcile-not-required-for-l1",
      blocking: false,
      detail: `L1 runner callback uses ${spec.callbackBaseUrl}; fixed HTTPS exposure remains owned by config/platform-infra/public-edge.yaml.`,
    }],
    ...(result.exitCode === 0 ? {} : {
      error: {
        code: "agentrun_native_service_failed",
        exitCode: result.exitCode,
        stderr: result.stderr.trim().slice(-2000),
      },
    }),
  };
}

function nativeEnvironment(spec: AgentRunNativeManagerSpec): NodeJS.ProcessEnv {
  const sourceCommitResult = runCommand(["git", "rev-parse", "HEAD"], spec.workspace, { timeoutMs: 5_000 });
  const sourceCommit = sourceCommitResult.stdout.trim();
  if (sourceCommitResult.exitCode !== 0 || !/^[0-9a-f]{40}$/u.test(sourceCommit)) {
    throw new Error(`cannot resolve AgentRun source commit from ${spec.workspace}`);
  }
  return {
    AGENTRUN_STORE: spec.store,
    AGENTRUN_API_KEY_FILE: secretSourcePath(spec.apiKeySourceRef),
    KUBECONFIG: spec.kubeconfig,
    AGENTRUN_KUBECTL_COMMAND: spec.kubectlCommand,
    AGENTRUN_SOURCE_COMMIT: sourceCommit,
    AGENTRUN_RUNTIME_NAMESPACE: spec.runtimeNamespace,
    AGENTRUN_LANE: spec.lane,
    AGENTRUN_INTERNAL_MGR_URL: spec.callbackBaseUrl,
    [`AGENTRUN_${spec.unideskSshEndpointEnv.name}`]: spec.unideskSshEndpointEnv.value,
    AGENTRUN_RUNNER_IMAGE: spec.runner.image,
    AGENTRUN_BOOT_REPO_URL: spec.runner.bootRepoUrl,
    AGENTRUN_RUNNER_SERVICE_ACCOUNT: spec.runner.serviceAccount,
    AGENTRUN_RUNNER_JOB_NAME_PREFIX: spec.runner.jobNamePrefix,
    AGENTRUN_RUNNER_NODE_SELECTOR_JSON: JSON.stringify(spec.runner.nodeSelector),
    AGENTRUN_RUNNER_API_KEY_SECRET_NAME: spec.runner.apiKeySecretRef.name,
    AGENTRUN_RUNNER_API_KEY_SECRET_KEY: spec.runner.apiKeySecretRef.key,
    AGENTRUN_RUNNER_IDLE_TIMEOUT_MS: String(spec.runner.idleTimeoutMs),
    AGENTRUN_BACKEND_RETRY_MAX_ATTEMPTS: String(spec.runner.backendRetry.maxAttempts),
    AGENTRUN_BACKEND_RETRY_INITIAL_BACKOFF_MS: String(spec.runner.backendRetry.initialBackoffMs),
    AGENTRUN_BACKEND_RETRY_MAX_BACKOFF_MS: String(spec.runner.backendRetry.maxBackoffMs),
    AGENTRUN_RUNNER_EGRESS_PROXY_URL: spec.runner.egressProxyUrl,
    AGENTRUN_RUNNER_NO_PROXY_EXTRA: spec.runner.noProxyExtra.join(","),
    AGENTRUN_RUNNER_DISPATCHER_ENABLED: "true",
    AGENTRUN_RUNNER_DISPATCHER_INTERVAL_MS: String(spec.dispatcher.intervalMs),
    AGENTRUN_RUNNER_DISPATCHER_BATCH_SIZE: String(spec.dispatcher.batchSize),
    AGENTRUN_RUNNER_DISPATCHER_LEASE_MS: String(spec.dispatcher.leaseMs),
    AGENTRUN_RUNNER_DISPATCHER_ATTEMPT_TIMEOUT_MS: String(spec.dispatcher.attemptTimeoutMs),
    AGENTRUN_RUNNER_DISPATCHER_MAX_ATTEMPTS: String(spec.dispatcher.maxAttempts),
    AGENTRUN_RUNNER_DISPATCHER_RETRY_BACKOFF_MS: String(spec.dispatcher.retryBackoffMs),
    AGENTRUN_MANAGER_RECONCILER_ENABLED: "true",
    AGENTRUN_MANAGER_RECONCILER_INTERVAL_MS: String(spec.reconciler.intervalMs),
    AGENTRUN_MANAGER_RECONCILER_BATCH_SIZE: String(spec.reconciler.batchSize),
    AGENTRUN_RUNNER_STARTUP_RETRY_MAX_ATTEMPTS: String(spec.startupRetry.maxAttempts),
    AGENTRUN_RUNNER_STARTUP_RETRY_INITIAL_BACKOFF_MS: String(spec.startupRetry.initialBackoffMs),
    AGENTRUN_RUNNER_STARTUP_RETRY_MAX_BACKOFF_MS: String(spec.startupRetry.maxBackoffMs),
    AGENTRUN_RUNNER_STARTUP_RETRY_MULTIPLIER: String(spec.startupRetry.multiplier),
    AGENTRUN_RUNNER_STARTUP_RETRY_TOTAL_DEADLINE_MS: String(spec.startupRetry.totalDeadlineMs),
    AGENTRUN_RUNNER_STARTUP_RETRY_JITTER_RATIO: String(spec.startupRetry.jitterRatio),
    AGENTRUN_KAFKA_ENABLED: "true",
    AGENTRUN_KAFKA_BOOTSTRAP_SERVERS: spec.kafka.bootstrapServers,
    AGENTRUN_KAFKA_DNS_SERVERS: spec.kafka.dnsServers.join(","),
    AGENTRUN_KAFKA_DNS_SEARCH_DOMAINS: spec.kafka.dnsSearchDomains.join(","),
    AGENTRUN_KAFKA_EVENT_TOPIC: spec.kafka.eventTopic,
    AGENTRUN_KAFKA_CLIENT_ID: spec.kafka.clientId,
    AGENTRUN_KAFKA_OUTBOX_INTERVAL_MS: String(spec.kafka.outboxIntervalMs),
    AGENTRUN_KAFKA_OUTBOX_BATCH_SIZE: String(spec.kafka.outboxBatchSize),
    AGENTRUN_KAFKA_OUTBOX_LEASE_MS: String(spec.kafka.outboxLeaseMs),
    AGENTRUN_KAFKA_OUTBOX_RETRY_BACKOFF_MS: String(spec.kafka.outboxRetryBackoffMs),
    AGENTRUN_KAFKA_STDIO_PRODUCE_ENABLED: "true",
    AGENTRUN_KAFKA_STDIO_TOPIC: spec.kafka.stdioTopic,
    AGENTRUN_KAFKA_STDIO_CLIENT_ID: spec.kafka.stdioClientId,
  };
}

function validateLocalInputs(spec: AgentRunNativeManagerSpec): void {
  if (!existsSync(spec.workspace)) throw new Error(`AgentRun native workspace is missing: ${spec.workspace}`);
  if (!existsSync(spec.kubeconfig)) throw new Error(`AgentRun native kubeconfig is missing: ${spec.kubeconfig}`);
  if (spec.stateDir !== ".state") throw new Error(`${CONFIG_PATH}#nativeDevelopment.manager.stateDir must be .state for the AgentRun server lifecycle`);
  const sourcePath = secretSourcePath(spec.apiKeySourceRef);
  const source = readEnvSourceFile({ root: "/root/.unidesk/.state/secrets", sourceRef: spec.apiKeySourceRef });
  requiredEnvValue(source.values, spec.apiKeySourceKey, spec.apiKeySourceRef);
  if (!existsSync(sourcePath)) throw new Error(`AgentRun native API key source is missing: ${spec.apiKeySourceRef}`);
}

function secretSourcePath(sourceRef: string): string {
  if (sourceRef.includes("..")) throw new Error("AgentRun native API key sourceRef must not contain ..");
  return isAbsolute(sourceRef) ? sourceRef : join("/root/.unidesk/.state/secrets", ...sourceRef.split("/"));
}

function readNativeManagerSpec(): AgentRunNativeManagerSpec {
  const root = readYamlRecord(rootPath(CONFIG_PATH), "AgentRunConfig");
  const manager = recordField(recordField(root, "nativeDevelopment", CONFIG_PATH), "manager", `${CONFIG_PATH}.nativeDevelopment`);
  const runner = recordField(manager, "runner", `${CONFIG_PATH}.nativeDevelopment.manager`);
  const runnerSecret = recordField(runner, "apiKeySecretRef", `${CONFIG_PATH}.nativeDevelopment.manager.runner`);
  const backendRetry = recordField(runner, "backendRetry", `${CONFIG_PATH}.nativeDevelopment.manager.runner`);
  const dispatcher = recordField(manager, "dispatcher", `${CONFIG_PATH}.nativeDevelopment.manager`);
  const reconciler = recordField(manager, "reconciler", `${CONFIG_PATH}.nativeDevelopment.manager`);
  const startupRetry = recordField(manager, "startupRetry", `${CONFIG_PATH}.nativeDevelopment.manager`);
  const kafka = recordField(manager, "kafka", `${CONFIG_PATH}.nativeDevelopment.manager`);
  const exposure = recordField(manager, "publicExposure", `${CONFIG_PATH}.nativeDevelopment.manager`);
  const unideskSshEndpointEnv = recordField(manager, "unideskSshEndpointEnv", `${CONFIG_PATH}.nativeDevelopment.manager`);
  const store = stringField(manager, "store", `${CONFIG_PATH}.nativeDevelopment.manager`);
  if (store !== "memory") throw new Error(`${CONFIG_PATH}.nativeDevelopment.manager.store must be memory`);
  const publicBaseUrl = stringField(exposure, "publicBaseUrl", `${CONFIG_PATH}.nativeDevelopment.manager.publicExposure`);
  if (!publicBaseUrl.startsWith("https://")) throw new Error(`${CONFIG_PATH}.nativeDevelopment.manager.publicExposure.publicBaseUrl must use https`);
  const callbackBaseUrl = stringField(manager, "callbackBaseUrl", `${CONFIG_PATH}.nativeDevelopment.manager`);
  const callbackHost = new URL(callbackBaseUrl).hostname;
  if (callbackHost === "127.0.0.1" || callbackHost === "localhost") throw new Error(`${CONFIG_PATH}.nativeDevelopment.manager.callbackBaseUrl must be reachable from runner Pods`);
  const unideskSshEndpointName = stringField(unideskSshEndpointEnv, "name", `${CONFIG_PATH}.nativeDevelopment.manager.unideskSshEndpointEnv`);
  if (!["UNIDESK_MAIN_SERVER_IP", "UNIDESK_MAIN_SERVER_HOST", "UNIDESK_FRONTEND_URL"].includes(unideskSshEndpointName)) {
    throw new Error(`${CONFIG_PATH}.nativeDevelopment.manager.unideskSshEndpointEnv.name is unsupported`);
  }
  return {
    workspace: stringField(manager, "workspace", `${CONFIG_PATH}.nativeDevelopment.manager`),
    bindHost: stringField(manager, "bindHost", `${CONFIG_PATH}.nativeDevelopment.manager`),
    probeHost: stringField(manager, "probeHost", `${CONFIG_PATH}.nativeDevelopment.manager`),
    port: positiveInteger(manager, "port", `${CONFIG_PATH}.nativeDevelopment.manager`),
    store,
    stateDir: stringField(manager, "stateDir", `${CONFIG_PATH}.nativeDevelopment.manager`),
    apiKeySourceRef: stringField(manager, "apiKeySourceRef", `${CONFIG_PATH}.nativeDevelopment.manager`),
    apiKeySourceKey: stringField(manager, "apiKeySourceKey", `${CONFIG_PATH}.nativeDevelopment.manager`),
    kubeconfig: stringField(manager, "kubeconfig", `${CONFIG_PATH}.nativeDevelopment.manager`),
    kubectlCommand: stringField(manager, "kubectlCommand", `${CONFIG_PATH}.nativeDevelopment.manager`),
    runtimeNamespace: stringField(manager, "runtimeNamespace", `${CONFIG_PATH}.nativeDevelopment.manager`),
    lane: stringField(manager, "lane", `${CONFIG_PATH}.nativeDevelopment.manager`),
    callbackBaseUrl,
    unideskSshEndpointEnv: {
      name: unideskSshEndpointName,
      value: stringField(unideskSshEndpointEnv, "value", `${CONFIG_PATH}.nativeDevelopment.manager.unideskSshEndpointEnv`),
    },
    runner: {
      image: stringField(runner, "image", `${CONFIG_PATH}.nativeDevelopment.manager.runner`),
      bootRepoUrl: stringField(runner, "bootRepoUrl", `${CONFIG_PATH}.nativeDevelopment.manager.runner`),
      serviceAccount: stringField(runner, "serviceAccount", `${CONFIG_PATH}.nativeDevelopment.manager.runner`),
      jobNamePrefix: stringField(runner, "jobNamePrefix", `${CONFIG_PATH}.nativeDevelopment.manager.runner`),
      nodeSelector: stringRecordField(runner, "nodeSelector", `${CONFIG_PATH}.nativeDevelopment.manager.runner`),
      apiKeySecretRef: {
        name: stringField(runnerSecret, "name", `${CONFIG_PATH}.nativeDevelopment.manager.runner.apiKeySecretRef`),
        key: stringField(runnerSecret, "key", `${CONFIG_PATH}.nativeDevelopment.manager.runner.apiKeySecretRef`),
      },
      idleTimeoutMs: positiveInteger(runner, "idleTimeoutMs", `${CONFIG_PATH}.nativeDevelopment.manager.runner`),
      backendRetry: positiveIntegerFields(backendRetry, ["maxAttempts", "initialBackoffMs", "maxBackoffMs"], `${CONFIG_PATH}.nativeDevelopment.manager.runner.backendRetry`),
      egressProxyUrl: stringField(runner, "egressProxyUrl", `${CONFIG_PATH}.nativeDevelopment.manager.runner`),
      noProxyExtra: stringArrayField(runner, "noProxyExtra", `${CONFIG_PATH}.nativeDevelopment.manager.runner`),
    },
    dispatcher: positiveIntegerFields(dispatcher, ["intervalMs", "batchSize", "leaseMs", "attemptTimeoutMs", "maxAttempts", "retryBackoffMs"], `${CONFIG_PATH}.nativeDevelopment.manager.dispatcher`),
    reconciler: positiveIntegerFields(reconciler, ["intervalMs", "batchSize"], `${CONFIG_PATH}.nativeDevelopment.manager.reconciler`),
    startupRetry: {
      ...positiveIntegerFields(startupRetry, ["maxAttempts", "initialBackoffMs", "maxBackoffMs", "totalDeadlineMs"], `${CONFIG_PATH}.nativeDevelopment.manager.startupRetry`),
      multiplier: numberField(startupRetry, "multiplier", `${CONFIG_PATH}.nativeDevelopment.manager.startupRetry`),
      jitterRatio: numberField(startupRetry, "jitterRatio", `${CONFIG_PATH}.nativeDevelopment.manager.startupRetry`),
    },
    kafka: {
      bootstrapServers: stringField(kafka, "bootstrapServers", `${CONFIG_PATH}.nativeDevelopment.manager.kafka`),
      dnsServers: stringArrayField(kafka, "dnsServers", `${CONFIG_PATH}.nativeDevelopment.manager.kafka`),
      dnsSearchDomains: stringArrayField(kafka, "dnsSearchDomains", `${CONFIG_PATH}.nativeDevelopment.manager.kafka`),
      eventTopic: stringField(kafka, "eventTopic", `${CONFIG_PATH}.nativeDevelopment.manager.kafka`),
      clientId: stringField(kafka, "clientId", `${CONFIG_PATH}.nativeDevelopment.manager.kafka`),
      stdioTopic: stringField(kafka, "stdioTopic", `${CONFIG_PATH}.nativeDevelopment.manager.kafka`),
      stdioClientId: stringField(kafka, "stdioClientId", `${CONFIG_PATH}.nativeDevelopment.manager.kafka`),
      ...positiveIntegerFields(kafka, ["outboxIntervalMs", "outboxBatchSize", "outboxLeaseMs", "outboxRetryBackoffMs"], `${CONFIG_PATH}.nativeDevelopment.manager.kafka`),
    },
    publicExposure: {
      publicBaseUrl,
      hostname: stringField(exposure, "hostname", `${CONFIG_PATH}.nativeDevelopment.manager.publicExposure`),
      expectedA: stringField(exposure, "expectedA", `${CONFIG_PATH}.nativeDevelopment.manager.publicExposure`),
      upstream: stringField(exposure, "upstream", `${CONFIG_PATH}.nativeDevelopment.manager.publicExposure`),
    },
  };
}

function positiveIntegerFields<T extends string>(record: Record<string, unknown>, fields: readonly T[], path: string): Record<T, number> {
  return Object.fromEntries(fields.map((field) => [field, positiveInteger(record, field, path)])) as Record<T, number>;
}

function stringRecordField(record: Record<string, unknown>, key: string, path: string): Record<string, string> {
  const value = recordField(record, key, path);
  const entries = Object.entries(value);
  if (entries.length === 0) throw new Error(`${path}.${key} must not be empty`);
  return Object.fromEntries(entries.map(([entryKey, entryValue]) => {
    if (entryKey.trim().length === 0 || typeof entryValue !== "string" || entryValue.trim().length === 0) {
      throw new Error(`${path}.${key} entries must use non-empty string keys and values`);
    }
    return [entryKey, entryValue.trim()];
  }));
}

function positiveInteger(record: Record<string, unknown>, key: string, path: string): number {
  const value = integerField(record, key, path);
  if (value < 1) throw new Error(`${path}.${key} must be positive`);
  return value;
}

function parsePayload(stdout: string): Record<string, unknown> {
  const parsed = JSON.parse(stdout) as unknown;
  return asRecord(parsed, "AgentRun native CLI output");
}

function nativeAction(value: string | undefined): NativeAction {
  if (value === "start" || value === "stop" || value === "restart" || value === "status" || value === "logs") return value;
  throw new Error("agentrun native-development manager action must be start, stop, restart, status, or logs");
}

function isHelpArg(value: string): boolean {
  return value === "--help" || value === "-h" || value === "help";
}
