import { formatCliCommand } from "../cli/command-format.js";
import { readConfigFileSnapshot, writeConfigFile, type OpenClawConfig } from "../config/config.js";
import { resolveSecretInputRef } from "../config/types.secrets.js";
import { shouldRequireGatewayTokenForInstall } from "../gateway/auth-install-policy.js";
import { hasAmbiguousGatewayAuthModeConfig } from "../gateway/auth-mode-policy.js";
import { resolveGatewayAuth } from "../gateway/auth.js";
import { readGatewayTokenEnv } from "../gateway/credentials.js";
import { secretRefKey } from "../secrets/ref-contract.js";
import { resolveSecretRefValues } from "../secrets/resolve.js";
import { randomToken } from "./onboard-helpers.js";

type GatewayInstallTokenOptions = {
  config: OpenClawConfig;
  env: NodeJS.ProcessEnv;
  explicitToken?: string;
  autoGenerateWhenMissing?: boolean;
  persistGeneratedToken?: boolean;
};

export type GatewayInstallTokenResolution = {
  token?: string;
  tokenRefConfigured: boolean;
  unavailableReason?: string;
  warnings: string[];
};

function resolveConfiguredGatewayInstallToken(params: {
  config: OpenClawConfig;
  env: NodeJS.ProcessEnv;
  explicitToken?: string;
  tokenRef: unknown;
}): string | undefined {
  const configToken =
    params.tokenRef || typeof params.config.gateway?.auth?.token !== "string"
      ? undefined
      : params.config.gateway.auth.token.trim() || undefined;
  const explicitToken = params.explicitToken?.trim() || undefined;
  const envToken = readGatewayTokenEnv(params.env);
  return explicitToken || configToken || (params.tokenRef ? undefined : envToken);
}

async function validateGatewayInstallTokenSecretRef(params: {
  tokenRef: NonNullable<ReturnType<typeof resolveSecretInputRef>["ref"]>;
  config: OpenClawConfig;
  env: NodeJS.ProcessEnv;
}): Promise<string | undefined> {
  try {
    const resolved = await resolveSecretRefValues([params.tokenRef], {
      config: params.config,
      env: params.env,
    });
    const value = resolved.get(secretRefKey(params.tokenRef));
    if (typeof value !== "string" || value.trim().length === 0) {
      throw new Error("gateway.auth.token resolved to an empty or non-string value.");
    }
    return undefined;
  } catch (err) {
    return `gateway.auth.token SecretRef is configured but unresolved (${String(err)}).`;
  }
}

async function maybePersistAutoGeneratedGatewayInstallToken(params: {
  token: string;
  config: OpenClawConfig;
  warnings: string[];
}): Promise<string | undefined> {
  try {
    const snapshot = await readConfigFileSnapshot();
    if (snapshot.exists && !snapshot.valid) {
      params.warnings.push(
        "Warning: config file exists but is invalid; skipping token persistence.",
      );
      return params.token;
    }

    const baseConfig = snapshot.exists ? snapshot.config : {};
    const existingTokenRef = resolveSecretInputRef({
      value: baseConfig.gateway?.auth?.token,
      defaults: baseConfig.secrets?.defaults,
    }).ref;
    const baseConfigToken =
      existingTokenRef || typeof baseConfig.gateway?.auth?.token !== "string"
        ? undefined
        : baseConfig.gateway.auth.token.trim() || undefined;
    if (!existingTokenRef && !baseConfigToken) {
      await writeConfigFile({
        ...baseConfig,
        gateway: {
          ...baseConfig.gateway,
          auth: {
            ...baseConfig.gateway?.auth,
            mode: baseConfig.gateway?.auth?.mode ?? "token",
            token: params.token,
          },
        },
      });
      return params.token;
    }
    if (baseConfigToken) {
      return baseConfigToken;
    }
    params.warnings.push(
      "Warning: gateway.auth.token is SecretRef-managed; skipping plaintext token persistence.",
    );
    return undefined;
  } catch (err) {
    params.warnings.push(`Warning: could not persist token to config: ${String(err)}`);
    return params.token;
  }
}

function formatAmbiguousGatewayAuthModeReason(): string {
  return [
    "gateway.auth.token and gateway.auth.password are both configured while gateway.auth.mode is unset.",
    `Set ${formatCliCommand("openclaw config set gateway.auth.mode token")} or ${formatCliCommand("openclaw config set gateway.auth.mode password")}.`,
  ].join(" ");
}

export async function resolveGatewayInstallToken(
  options: GatewayInstallTokenOptions,
): Promise<GatewayInstallTokenResolution> {
  const cfg = options.config;
  const warnings: string[] = [];
  const tokenRef = resolveSecretInputRef({
    value: cfg.gateway?.auth?.token,
    defaults: cfg.secrets?.defaults,
  }).ref;
  const tokenRefConfigured = Boolean(tokenRef);

  if (hasAmbiguousGatewayAuthModeConfig(cfg)) {
    return {
      token: undefined,
      tokenRefConfigured,
      unavailableReason: formatAmbiguousGatewayAuthModeReason(),
      warnings,
    };
  }

  const resolvedAuth = resolveGatewayAuth({
    authConfig: cfg.gateway?.auth,
    env: options.env,
    tailscaleMode: cfg.gateway?.tailscale?.mode ?? "off",
  });
  const needsToken =
    shouldRequireGatewayTokenForInstall(cfg, options.env) && !resolvedAuth.allowTailscale;

  let token = resolveConfiguredGatewayInstallToken({
    config: cfg,
    env: options.env,
    explicitToken: options.explicitToken,
    tokenRef,
  });
  let unavailableReason: string | undefined;

  if (tokenRef && !token && needsToken) {
    unavailableReason = await validateGatewayInstallTokenSecretRef({
      tokenRef,
      config: cfg,
      env: options.env,
    });
    if (!unavailableReason) {
      warnings.push(
        "gateway.auth.token is SecretRef-managed; install will not persist a resolved token in service environment. Ensure the SecretRef is resolvable in the daemon runtime context.",
      );
    }
  }

  const allowAutoGenerate = options.autoGenerateWhenMissing ?? false;
  const persistGeneratedToken = options.persistGeneratedToken ?? false;
  if (!token && needsToken && !tokenRef && allowAutoGenerate) {
    token = randomToken();
    warnings.push(
      persistGeneratedToken
        ? "No gateway token found. Auto-generated one and saving to config."
        : "No gateway token found. Auto-generated one for this run without saving to config.",
    );

    if (persistGeneratedToken) {
      token = await maybePersistAutoGeneratedGatewayInstallToken({
        token,
        config: cfg,
        warnings,
      });
    }
  }

  return {
    token,
    tokenRefConfigured,
    unavailableReason,
    warnings,
  };
}
