/**
 * Security module: token validation, rate limiting, input sanitization, user allowlist.
 */

import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
import {
  createFixedWindowRateLimiter,
  type FixedWindowRateLimiter,
} from "openclaw/plugin-sdk/webhook-ingress";

/**
 * Validate webhook token using constant-time comparison.
 * Reject empty tokens explicitly; use shared constant-time comparison otherwise.
 */
export function validateToken(received: string, expected: string): boolean {
  if (!received || !expected) {
    return false;
  }
  return safeEqualSecret(received, expected);
}

export async function authorizeUserForDmWithIngress(params: {
  accountId: string;
  userId: string;
  dmPolicy: "open" | "allowlist" | "disabled";
  allowedUserIds: string[];
}) {
  return await resolveStableChannelMessageIngress({
    channelId: "synology-chat",
    accountId: params.accountId,
    identity: {
      key: "sender-id",
      entryIdPrefix: "synology-chat-entry",
    },
    subject: { stableId: params.userId },
    conversation: {
      kind: "direct",
      id: "direct",
    },
    event: { mayPair: false },
    dmPolicy: params.dmPolicy,
    allowFrom: params.allowedUserIds,
  });
}

/**
 * Sanitize user input to prevent prompt injection attacks.
 * Filters known dangerous patterns and truncates long messages.
 */
export function sanitizeInput(text: string): string {
  const dangerousPatterns = [
    /ignore\s+(all\s+)?(previous|prior|above)\s+(instructions?|prompts?)/gi,
    /you\s+are\s+now\s+/gi,
    /system:\s*/gi,
    /<\|.*?\|>/g, // special tokens
  ];

  let sanitized = text;
  for (const pattern of dangerousPatterns) {
    sanitized = sanitized.replace(pattern, "[FILTERED]");
  }

  const maxLength = 4000;
  if (sanitized.length > maxLength) {
    sanitized = truncateUtf16Safe(sanitized, maxLength) + "... [truncated]";
  }

  return sanitized;
}

/**
 * Sliding window rate limiter per user ID.
 */
export class RateLimiter {
  private readonly limiter: FixedWindowRateLimiter;
  private readonly limit: number;

  constructor(limit = 30, windowSeconds = 60, maxTrackedUsers = 5_000) {
    this.limit = limit;
    const windowMs = finiteSecondsToTimerSafeMilliseconds(windowSeconds) ?? 1;
    this.limiter = createFixedWindowRateLimiter({
      windowMs,
      maxRequests: Math.max(1, Math.floor(limit)),
      maxTrackedKeys: Math.max(1, Math.floor(maxTrackedUsers)),
    });
  }

  /** Returns true if the request is allowed, false if rate-limited. */
  check(userId: string): boolean {
    return !this.limiter.isRateLimited(userId);
  }

  /** Exposed for tests and diagnostics. */
  size(): number {
    return this.limiter.size();
  }

  /** Exposed for tests and account lifecycle cleanup. */
  clear(): void {
    this.limiter.clear();
  }

  /** Exposed for tests. */
  maxRequests(): number {
    return this.limit;
  }
}
