import type { Command } from "commander";
import { danger } from "../globals.js";
import {
  type GmailRunOptions,
  type GmailSetupOptions,
  runGmailService,
  runGmailSetup,
} from "../hooks/gmail-ops.js";
import {
  DEFAULT_GMAIL_LABEL,
  DEFAULT_GMAIL_MAX_BYTES,
  DEFAULT_GMAIL_RENEW_MINUTES,
  DEFAULT_GMAIL_SERVE_BIND,
  DEFAULT_GMAIL_SERVE_PATH,
  DEFAULT_GMAIL_SERVE_PORT,
  DEFAULT_GMAIL_SUBSCRIPTION,
  DEFAULT_GMAIL_TOPIC,
} from "../hooks/gmail.js";
import { defaultRuntime } from "../runtime.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { formatDocsLink } from "../terminal/links.js";
import { theme } from "../terminal/theme.js";

export function registerWebhooksCli(program: Command) {
  const webhooks = program
    .command("webhooks")
    .description("Webhook helpers and integrations")
    .addHelpText(
      "after",
      () =>
        `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/webhooks", "docs.openclaw.ai/cli/webhooks")}\n`,
    );

  const gmail = webhooks.command("gmail").description("Gmail Pub/Sub hooks (via gogcli)");

  gmail
    .command("setup")
    .description("Configure Gmail watch + Pub/Sub + OpenClaw hooks")
    .requiredOption("--account <email>", "Gmail account to watch")
    .option("--project <id>", "GCP project id (OAuth client owner)")
    .option("--topic <name>", "Pub/Sub topic name", DEFAULT_GMAIL_TOPIC)
    .option("--subscription <name>", "Pub/Sub subscription name", DEFAULT_GMAIL_SUBSCRIPTION)
    .option("--label <label>", "Gmail label to watch", DEFAULT_GMAIL_LABEL)
    .option("--hook-url <url>", "OpenClaw hook URL")
    .option("--hook-token <token>", "OpenClaw hook token")
    .option("--push-token <token>", "Push token for gog watch serve")
    .option("--bind <host>", "gog watch serve bind host", DEFAULT_GMAIL_SERVE_BIND)
    .option("--port <port>", "gog watch serve port", String(DEFAULT_GMAIL_SERVE_PORT))
    .option("--path <path>", "gog watch serve path", DEFAULT_GMAIL_SERVE_PATH)
    .option("--include-body", "Include email body snippets", true)
    .option("--max-bytes <n>", "Max bytes for body snippets", String(DEFAULT_GMAIL_MAX_BYTES))
    .option(
      "--renew-minutes <n>",
      "Renew watch every N minutes",
      String(DEFAULT_GMAIL_RENEW_MINUTES),
    )
    .option("--tailscale <mode>", "Expose push endpoint via tailscale (funnel|serve|off)", "funnel")
    .option("--tailscale-path <path>", "Path for tailscale serve/funnel")
    .option(
      "--tailscale-target <target>",
      "Tailscale serve/funnel target (port, host:port, or URL)",
    )
    .option("--push-endpoint <url>", "Explicit Pub/Sub push endpoint")
    .option("--json", "Output JSON summary", false)
    .action(async (opts) => {
      try {
        const parsed = parseGmailSetupOptions(opts);
        await runGmailSetup(parsed);
      } catch (err) {
        defaultRuntime.error(danger(String(err)));
        defaultRuntime.exit(1);
      }
    });

  gmail
    .command("run")
    .description("Run gog watch serve + auto-renew loop")
    .option("--account <email>", "Gmail account to watch")
    .option("--topic <topic>", "Pub/Sub topic path (projects/.../topics/..)")
    .option("--subscription <name>", "Pub/Sub subscription name")
    .option("--label <label>", "Gmail label to watch")
    .option("--hook-url <url>", "OpenClaw hook URL")
    .option("--hook-token <token>", "OpenClaw hook token")
    .option("--push-token <token>", "Push token for gog watch serve")
    .option("--bind <host>", "gog watch serve bind host")
    .option("--port <port>", "gog watch serve port")
    .option("--path <path>", "gog watch serve path")
    .option("--include-body", "Include email body snippets")
    .option("--max-bytes <n>", "Max bytes for body snippets")
    .option("--renew-minutes <n>", "Renew watch every N minutes")
    .option("--tailscale <mode>", "Expose push endpoint via tailscale (funnel|serve|off)")
    .option("--tailscale-path <path>", "Path for tailscale serve/funnel")
    .option(
      "--tailscale-target <target>",
      "Tailscale serve/funnel target (port, host:port, or URL)",
    )
    .action(async (opts) => {
      try {
        const parsed = parseGmailRunOptions(opts);
        await runGmailService(parsed);
      } catch (err) {
        defaultRuntime.error(danger(String(err)));
        defaultRuntime.exit(1);
      }
    });
}

function parseGmailSetupOptions(raw: Record<string, unknown>): GmailSetupOptions {
  const accountRaw = raw.account;
  const account = normalizeOptionalString(accountRaw) ?? "";
  if (!account) {
    throw new Error("--account is required");
  }
  const common = parseGmailCommonOptions(raw);
  return {
    account,
    project: normalizeOptionalString(raw.project),
    ...gmailOptionsFromCommon(common),
    pushEndpoint: normalizeOptionalString(raw.pushEndpoint),
    json: Boolean(raw.json),
  };
}

function parseGmailRunOptions(raw: Record<string, unknown>): GmailRunOptions {
  const common = parseGmailCommonOptions(raw);
  return {
    account: normalizeOptionalString(raw.account),
    ...gmailOptionsFromCommon(common),
  };
}

function parseGmailCommonOptions(raw: Record<string, unknown>) {
  return {
    topic: normalizeOptionalString(raw.topic),
    subscription: normalizeOptionalString(raw.subscription),
    label: normalizeOptionalString(raw.label),
    hookUrl: normalizeOptionalString(raw.hookUrl),
    hookToken: normalizeOptionalString(raw.hookToken),
    pushToken: normalizeOptionalString(raw.pushToken),
    bind: normalizeOptionalString(raw.bind),
    port: numberOption(raw.port),
    path: normalizeOptionalString(raw.path),
    includeBody: booleanOption(raw.includeBody),
    maxBytes: numberOption(raw.maxBytes),
    renewEveryMinutes: numberOption(raw.renewMinutes),
    tailscaleRaw: normalizeOptionalString(raw.tailscale),
    tailscalePath: normalizeOptionalString(raw.tailscalePath),
    tailscaleTarget: normalizeOptionalString(raw.tailscaleTarget),
  };
}

function gmailOptionsFromCommon(
  common: ReturnType<typeof parseGmailCommonOptions>,
): Omit<GmailRunOptions, "account"> {
  return {
    topic: common.topic,
    subscription: common.subscription,
    label: common.label,
    hookUrl: common.hookUrl,
    hookToken: common.hookToken,
    pushToken: common.pushToken,
    bind: common.bind,
    port: common.port,
    path: common.path,
    includeBody: common.includeBody,
    maxBytes: common.maxBytes,
    renewEveryMinutes: common.renewEveryMinutes,
    tailscale: common.tailscaleRaw as GmailRunOptions["tailscale"],
    tailscalePath: common.tailscalePath,
    tailscaleTarget: common.tailscaleTarget,
  };
}

function numberOption(value: unknown): number | undefined {
  if (value === undefined || value === null) {
    return undefined;
  }
  const n = typeof value === "number" ? value : Number(value);
  if (!Number.isFinite(n) || n <= 0) {
    return undefined;
  }
  return Math.floor(n);
}

function booleanOption(value: unknown): boolean | undefined {
  if (value === undefined || value === null) {
    return undefined;
  }
  return Boolean(value);
}
