/**
 * Per-admin rate limiting for sensitive write actions (Phase 5).
 *
 * The design (docs/superpowers/plans/2026-05-12-ssh-tool-integration.md Phase 5)
 * caps `POST /api/ssh/admin/connections/:id/force-unlock` at 10 calls per hour
 * per admin user — force-unlock bypasses the SSH abuse lockout that protects
 * targets from credential-stuffing-style probing, so a runaway admin or a
 * compromised admin session shouldn't be able to do it without limit.
 *
 * Single-process orchestrator: in-memory token bucket per userId is sufficient.
 * If this ever scales to multiple processes the limiter must move to the DB.
 */

export interface RateLimitDecision {
  allowed: boolean;
  /** Seconds until the next slot frees up (only when !allowed). */
  retryAfterSeconds?: number;
}

export interface AdminRateLimiter {
  check(userId: string, now?: Date): RateLimitDecision;
  /** Reset a user's bucket. For tests / admin tooling. */
  reset(userId: string): void;
  /** Reset all buckets. Tests. */
  resetAll(): void;
}

interface Bucket {
  count: number;
  windowStart: number;
}

export interface RateLimitConfig {
  windowMs: number;
  maxRequests: number;
}

export const FORCE_UNLOCK_LIMIT: RateLimitConfig = {
  windowMs: 60 * 60 * 1000,
  maxRequests: 10,
};

/**
 * Create a token-bucket-ish limiter. Implementation is a fixed-window counter
 * — simpler and sufficient for the threat model; bursts at window boundaries
 * are acceptable because the next check after the boundary still triggers an
 * audit row (force-unlock is always audited).
 */
export function createAdminRateLimiter(config: RateLimitConfig = FORCE_UNLOCK_LIMIT): AdminRateLimiter {
  const buckets = new Map<string, Bucket>();
  return {
    check(userId, now = new Date()): RateLimitDecision {
      const ms = now.getTime();
      const bucket = buckets.get(userId);
      if (!bucket || ms - bucket.windowStart >= config.windowMs) {
        buckets.set(userId, { count: 1, windowStart: ms });
        return { allowed: true };
      }
      if (bucket.count < config.maxRequests) {
        bucket.count += 1;
        return { allowed: true };
      }
      const retryAfterMs = config.windowMs - (ms - bucket.windowStart);
      return { allowed: false, retryAfterSeconds: Math.max(1, Math.ceil(retryAfterMs / 1000)) };
    },
    reset(userId) {
      buckets.delete(userId);
    },
    resetAll() {
      buckets.clear();
    },
  };
}
