/**
 * Cache and rate limiting utility functions
 */

import type { RecommendRequest, Env } from '../types';
import { LIMITS } from '../config';

/**
 * Simple hash function for strings
 */
export function hashString(str: string): string {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash; // Convert to 32-bit integer
  }
  // Use >>> 0 to convert to unsigned 32-bit integer
  return (hash >>> 0).toString(36);
}

/**
 * Sanitize special characters for cache key
 */
export function sanitizeCacheValue(value: string): string {
  // Use URL-safe base64 encoding to avoid collisions
  try {
    return btoa(value).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
  } catch {
    // Fallback for non-ASCII characters
    return encodeURIComponent(value).replace(/[%]/g, '_');
  }
}

/**
 * Generate cache key from request parameters
 */
export function generateCacheKey(req: RecommendRequest): string {
  // Don't mutate original arrays - create sorted copies
  const sortedStack = [...req.tech_stack].sort();
  const sanitizedStack = sortedStack.map(sanitizeCacheValue).join(',');

  // Hash use_case to avoid special characters and length issues
  const useCaseHash = hashString(req.use_case);

  const parts = [
    `stack:${sanitizedStack}`,
    `users:${req.expected_users}`,
    `case:${useCaseHash}`,
  ];

  if (req.traffic_pattern) {
    parts.push(`traffic:${req.traffic_pattern}`);
  }

  if (req.budget_limit) {
    parts.push(`budget:${req.budget_limit}`);
  }

  // Include region preference in cache key
  if (req.region_preference && req.region_preference.length > 0) {
    const sortedRegions = [...req.region_preference].sort();
    const sanitizedRegions = sortedRegions.map(sanitizeCacheValue).join(',');
    parts.push(`region:${sanitizedRegions}`);
  }

  // Include language in cache key
  if (req.lang) {
    parts.push(`lang:${req.lang}`);
  }

  // Include CDN options in cache key
  if (req.cdn_enabled !== undefined) {
    parts.push(`cdn:${req.cdn_enabled}`);
  }
  if (req.cdn_cache_hit_rate !== undefined) {
    parts.push(`cdnrate:${req.cdn_cache_hit_rate}`);
  }

  return `recommend:${parts.join('|')}`;
}

// In-memory fallback for rate limiting when CACHE KV is unavailable
const inMemoryRateLimit = new Map<string, { count: number; resetTime: number }>();

/**
 * Rate limiting check using KV storage with in-memory fallback
 */
export async function checkRateLimit(clientIP: string, env: Env): Promise<{ allowed: boolean; requestId: string }> {
  const requestId = crypto.randomUUID();
  const now = Date.now();
  const maxRequests = LIMITS.RATE_LIMIT_MAX_REQUESTS;
  const windowMs = LIMITS.RATE_LIMIT_WINDOW_MS;

  // Use in-memory fallback if CACHE unavailable
  if (!env.CACHE) {
    const record = inMemoryRateLimit.get(clientIP);

    if (!record || record.resetTime < now) {
      // New window or expired
      inMemoryRateLimit.set(clientIP, { count: 1, resetTime: now + windowMs });
      return { allowed: true, requestId };
    }

    if (record.count >= maxRequests) {
      return { allowed: false, requestId };
    }

    // Increment count
    record.count++;
    return { allowed: true, requestId };
  }

  // KV-based rate limiting
  const kvKey = `ratelimit:${clientIP}`;

  try {
    const recordJson = await env.CACHE.get(kvKey);
    const record = recordJson ? JSON.parse(recordJson) as { count: number; resetTime: number } : null;

    if (!record || record.resetTime < now) {
      // New window
      await env.CACHE.put(
        kvKey,
        JSON.stringify({ count: 1, resetTime: now + windowMs }),
        { expirationTtl: 60 }
      );
      return { allowed: true, requestId };
    }

    if (record.count >= maxRequests) {
      return { allowed: false, requestId };
    }

    // Increment count
    record.count++;
    await env.CACHE.put(
      kvKey,
      JSON.stringify(record),
      { expirationTtl: 60 }
    );
    return { allowed: true, requestId };
  } catch (error) {
    console.error('[RateLimit] KV error:', error);
    // On error, deny the request (fail closed) for security
    return { allowed: false, requestId };
  }
}
