import { randomBytes } from "node:crypto";

import type {
  ClawdisConfig,
  HooksGmailTailscaleMode,
} from "../config/config.js";

export const DEFAULT_GMAIL_LABEL = "INBOX";
export const DEFAULT_GMAIL_TOPIC = "gog-gmail-watch";
export const DEFAULT_GMAIL_SUBSCRIPTION = "gog-gmail-watch-push";
export const DEFAULT_GMAIL_SERVE_BIND = "127.0.0.1";
export const DEFAULT_GMAIL_SERVE_PORT = 8788;
export const DEFAULT_GMAIL_SERVE_PATH = "/gmail-pubsub";
export const DEFAULT_GMAIL_MAX_BYTES = 20_000;
export const DEFAULT_GMAIL_RENEW_MINUTES = 12 * 60;
export const DEFAULT_HOOKS_PATH = "/hooks";
export const DEFAULT_HOOKS_BASE_URL = "http://127.0.0.1:18789";

export type GmailHookOverrides = {
  account?: string;
  label?: string;
  topic?: string;
  subscription?: string;
  pushToken?: string;
  hookToken?: string;
  hookUrl?: string;
  includeBody?: boolean;
  maxBytes?: number;
  renewEveryMinutes?: number;
  serveBind?: string;
  servePort?: number;
  servePath?: string;
  tailscaleMode?: HooksGmailTailscaleMode;
  tailscalePath?: string;
};

export type GmailHookRuntimeConfig = {
  account: string;
  label: string;
  topic: string;
  subscription: string;
  pushToken: string;
  hookToken: string;
  hookUrl: string;
  includeBody: boolean;
  maxBytes: number;
  renewEveryMinutes: number;
  serve: {
    bind: string;
    port: number;
    path: string;
  };
  tailscale: {
    mode: HooksGmailTailscaleMode;
    path: string;
  };
};

export function generateHookToken(bytes = 24): string {
  return randomBytes(bytes).toString("hex");
}

export function mergeHookPresets(
  existing: string[] | undefined,
  preset: string,
): string[] {
  const next = new Set(
    (existing ?? []).map((item) => item.trim()).filter(Boolean),
  );
  next.add(preset);
  return Array.from(next);
}

export function normalizeHooksPath(raw?: string): string {
  const base = raw?.trim() || DEFAULT_HOOKS_PATH;
  if (base === "/") return DEFAULT_HOOKS_PATH;
  const withSlash = base.startsWith("/") ? base : `/${base}`;
  return withSlash.replace(/\/+$/, "");
}

export function normalizeServePath(raw?: string): string {
  const base = raw?.trim() || DEFAULT_GMAIL_SERVE_PATH;
  const withSlash = base.startsWith("/") ? base : `/${base}`;
  return withSlash.replace(/\/+$/, "");
}

export function buildDefaultHookUrl(hooksPath?: string): string {
  const basePath = normalizeHooksPath(hooksPath);
  return joinUrl(DEFAULT_HOOKS_BASE_URL, `${basePath}/gmail`);
}

export function resolveGmailHookRuntimeConfig(
  cfg: ClawdisConfig,
  overrides: GmailHookOverrides,
): { ok: true; value: GmailHookRuntimeConfig } | { ok: false; error: string } {
  const hooks = cfg.hooks;
  const gmail = hooks?.gmail;
  const hookToken = overrides.hookToken ?? hooks?.token ?? "";
  if (!hookToken) {
    return { ok: false, error: "hooks.token missing (needed for gmail hook)" };
  }

  const account = overrides.account ?? gmail?.account ?? "";
  if (!account) {
    return { ok: false, error: "gmail account required" };
  }

  const topic = overrides.topic ?? gmail?.topic ?? "";
  if (!topic) {
    return { ok: false, error: "gmail topic required" };
  }

  const subscription =
    overrides.subscription ?? gmail?.subscription ?? DEFAULT_GMAIL_SUBSCRIPTION;

  const pushToken = overrides.pushToken ?? gmail?.pushToken ?? "";
  if (!pushToken) {
    return { ok: false, error: "gmail push token required" };
  }

  const hookUrl =
    overrides.hookUrl ?? gmail?.hookUrl ?? buildDefaultHookUrl(hooks?.path);

  const includeBody = overrides.includeBody ?? gmail?.includeBody ?? true;

  const maxBytesRaw = overrides.maxBytes ?? gmail?.maxBytes;
  const maxBytes =
    typeof maxBytesRaw === "number" &&
    Number.isFinite(maxBytesRaw) &&
    maxBytesRaw > 0
      ? Math.floor(maxBytesRaw)
      : DEFAULT_GMAIL_MAX_BYTES;

  const renewEveryMinutesRaw =
    overrides.renewEveryMinutes ?? gmail?.renewEveryMinutes;
  const renewEveryMinutes =
    typeof renewEveryMinutesRaw === "number" &&
    Number.isFinite(renewEveryMinutesRaw) &&
    renewEveryMinutesRaw > 0
      ? Math.floor(renewEveryMinutesRaw)
      : DEFAULT_GMAIL_RENEW_MINUTES;

  const serveBind =
    overrides.serveBind ?? gmail?.serve?.bind ?? DEFAULT_GMAIL_SERVE_BIND;
  const servePortRaw = overrides.servePort ?? gmail?.serve?.port;
  const servePort =
    typeof servePortRaw === "number" &&
    Number.isFinite(servePortRaw) &&
    servePortRaw > 0
      ? Math.floor(servePortRaw)
      : DEFAULT_GMAIL_SERVE_PORT;
  const servePath = normalizeServePath(
    overrides.servePath ?? gmail?.serve?.path,
  );

  const tailscaleMode =
    overrides.tailscaleMode ?? gmail?.tailscale?.mode ?? "off";
  const tailscalePath = normalizeServePath(
    overrides.tailscalePath ?? gmail?.tailscale?.path ?? servePath,
  );

  return {
    ok: true,
    value: {
      account,
      label: overrides.label ?? gmail?.label ?? DEFAULT_GMAIL_LABEL,
      topic,
      subscription,
      pushToken,
      hookToken,
      hookUrl,
      includeBody,
      maxBytes,
      renewEveryMinutes,
      serve: {
        bind: serveBind,
        port: servePort,
        path: servePath,
      },
      tailscale: {
        mode: tailscaleMode,
        path: tailscalePath,
      },
    },
  };
}

export function buildGogWatchStartArgs(
  cfg: Pick<GmailHookRuntimeConfig, "account" | "label" | "topic">,
): string[] {
  return [
    "gmail",
    "watch",
    "start",
    "--account",
    cfg.account,
    "--label",
    cfg.label,
    "--topic",
    cfg.topic,
  ];
}

export function buildGogWatchServeArgs(cfg: GmailHookRuntimeConfig): string[] {
  const args = [
    "gmail",
    "watch",
    "serve",
    "--account",
    cfg.account,
    "--bind",
    cfg.serve.bind,
    "--port",
    String(cfg.serve.port),
    "--path",
    cfg.serve.path,
    "--token",
    cfg.pushToken,
    "--hook-url",
    cfg.hookUrl,
    "--hook-token",
    cfg.hookToken,
  ];
  if (cfg.includeBody) {
    args.push("--include-body");
  }
  if (cfg.maxBytes > 0) {
    args.push("--max-bytes", String(cfg.maxBytes));
  }
  return args;
}

export function buildTopicPath(projectId: string, topicName: string): string {
  return `projects/${projectId}/topics/${topicName}`;
}

export function parseTopicPath(
  topic: string,
): { projectId: string; topicName: string } | null {
  const match = topic.trim().match(/^projects\/([^/]+)\/topics\/([^/]+)$/i);
  if (!match) return null;
  return { projectId: match[1] ?? "", topicName: match[2] ?? "" };
}

function joinUrl(base: string, path: string): string {
  const url = new URL(base);
  const basePath = url.pathname.replace(/\/+$/, "");
  const extra = path.startsWith("/") ? path : `/${path}`;
  url.pathname = `${basePath}${extra}`;
  return url.toString();
}
