/**
 * Enhanced Rate Limiting Generator
 * Generates comprehensive rate limiting infrastructure
 *
 * Features:
 * - Express rate limiting middleware
 * - Redis-based distributed rate limiting
 * - Per-user/IP/API key rate limits
 * - Different strategies (sliding window, fixed window, token bucket)
 * - Rate limit headers (X-RateLimit-*)
 * - Custom rate limit responses
 * - Rate limit bypass for admin/internal requests
 */

import { GeneratorContext, GeneratedFile, GenerationResult } from '../types';
import { RateLimiting } from '../../types/workflow';
import * as path from 'path';

export class EnhancedRateLimitingGenerator {
  readonly id = 'enhanced-rate-limiting';
  readonly name = 'Enhanced Rate Limiting Generator';
  readonly description = 'Generates comprehensive rate limiting infrastructure with multiple strategies and storage backends';

  generate(context: GeneratorContext): GenerationResult {
    const { workflow, outputDirectory } = context;
    const files: GeneratedFile[] = [];

    if (!workflow.rate_limiting) {
      return { files };
    }

    const rateLimiting = workflow.rate_limiting;

    // Generate rate limiting configuration
    files.push(this.generateRateLimitConfig(rateLimiting, outputDirectory));

    // Generate rate limiting middleware
    if (rateLimiting.middleware !== false) {
      files.push(this.generateRateLimitMiddleware(rateLimiting, outputDirectory));
    }

    // Generate storage backend (Redis or Memory)
    if (rateLimiting.storage?.type === 'redis') {
      files.push(this.generateRedisStore(rateLimiting, outputDirectory));
    } else {
      files.push(this.generateMemoryStore(rateLimiting, outputDirectory));
    }

    // Generate rate limit strategies
    files.push(this.generateRateLimitStrategies(rateLimiting, outputDirectory));

    // Generate rate limit types
    files.push(this.generateRateLimitTypes(rateLimiting, outputDirectory));

    return { files };
  }

  private generateRateLimitConfig(rateLimiting: RateLimiting, outputDirectory: string): GeneratedFile {
    const content = `/**
 * Rate Limiting Configuration
 * Generated by Bangstack
 */

export const rateLimitConfig = {
  middleware: ${rateLimiting.middleware !== false},
  storage: {
    type: '${rateLimiting.storage?.type || 'memory'}',
    ${rateLimiting.storage?.redis_url ? `redisUrl: process.env.REDIS_URL || '${rateLimiting.storage.redis_url}',` : ''}
  },
  strategy: '${rateLimiting.strategy || 'sliding_window'}',
  ${rateLimiting.per_user ? `perUser: {
    requests: ${rateLimiting.per_user.requests},
    window: '${rateLimiting.per_user.window}',
    ${rateLimiting.per_user.burst ? `burst: ${rateLimiting.per_user.burst},` : ''}
  },` : ''}
  ${rateLimiting.per_ip ? `perIp: {
    requests: ${rateLimiting.per_ip.requests},
    window: '${rateLimiting.per_ip.window}',
    ${rateLimiting.per_ip.burst ? `burst: ${rateLimiting.per_ip.burst},` : ''}
  },` : ''}
  ${rateLimiting.per_api_key ? `perApiKey: {
    requests: ${rateLimiting.per_api_key.requests},
    window: '${rateLimiting.per_api_key.window}',
    ${rateLimiting.per_api_key.burst ? `burst: ${rateLimiting.per_api_key.burst},` : ''}
  },` : ''}
  headers: ${rateLimiting.headers !== false},
  ${rateLimiting.custom_response ? `customResponse: {
    statusCode: ${rateLimiting.custom_response.status_code || 429},
    message: '${rateLimiting.custom_response.message || 'Too many requests'}',
  },` : ''}
  ${rateLimiting.bypass ? `bypass: {
    admin: ${rateLimiting.bypass.admin || false},
    internal: ${rateLimiting.bypass.internal || false},
    ${rateLimiting.bypass.api_keys ? `apiKeys: ${JSON.stringify(rateLimiting.bypass.api_keys)},` : ''}
  },` : ''}
};

export default rateLimitConfig;
`;

    return {
      path: path.join(outputDirectory, 'api/src/config/rate-limiting.ts'),
      content,
    };
  }

  private generateRateLimitMiddleware(rateLimiting: RateLimiting, outputDirectory: string): GeneratedFile {
    const content = `/**
 * Rate Limiting Middleware
 * Generated by Bangstack
 */

import { Request, Response, NextFunction } from 'express';
import { rateLimitConfig } from '../config/rate-limiting';
import { RateLimitStore } from './rate-limit-store';
import {
  SlidingWindowStrategy,
  FixedWindowStrategy,
  TokenBucketStrategy,
  RateLimitStrategy
} from './rate-limit-strategies';

// Initialize store
const store = new RateLimitStore();

// Initialize strategy
let strategy: RateLimitStrategy;
switch (rateLimitConfig.strategy) {
  case 'sliding_window':
    strategy = new SlidingWindowStrategy(store);
    break;
  case 'fixed_window':
    strategy = new FixedWindowStrategy(store);
    break;
  case 'token_bucket':
    strategy = new TokenBucketStrategy(store);
    break;
  default:
    strategy = new SlidingWindowStrategy(store);
}

/**
 * Rate limit middleware
 */
export function rateLimit(
  requests: number = 100,
  window: string = '1m'
) {
  return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      // Check bypass conditions
      if (shouldBypass(req)) {
        return next();
      }

      // Get identifier (user, IP, or API key)
      const identifier = getIdentifier(req);

      // Check rate limit
      const result = await strategy.checkLimit(identifier, requests, window);

      // Set rate limit headers
      if (rateLimitConfig.headers) {
        res.setHeader('X-RateLimit-Limit', result.limit);
        res.setHeader('X-RateLimit-Remaining', result.remaining);
        res.setHeader('X-RateLimit-Reset', result.reset);
      }

      if (!result.allowed) {
        const statusCode = rateLimitConfig.customResponse?.statusCode || 429;
        const message = rateLimitConfig.customResponse?.message || 'Too many requests';

        res.status(statusCode).json({
          error: {
            message,
            retryAfter: result.retryAfter,
          },
        });
        return;
      }

      next();
    } catch (error) {
      console.error('Rate limit error:', error);
      // Fail open - allow request if rate limiting fails
      next();
    }
  };
}

/**
 * Per-user rate limit
 */
export function rateLimitPerUser() {
  const config = rateLimitConfig.perUser;
  if (!config) {
    throw new Error('Per-user rate limiting not configured');
  }
  return rateLimit(config.requests, config.window);
}

/**
 * Per-IP rate limit
 */
export function rateLimitPerIp() {
  const config = rateLimitConfig.perIp;
  if (!config) {
    throw new Error('Per-IP rate limiting not configured');
  }
  return rateLimit(config.requests, config.window);
}

/**
 * Per-API key rate limit
 */
export function rateLimitPerApiKey() {
  const config = rateLimitConfig.perApiKey;
  if (!config) {
    throw new Error('Per-API key rate limiting not configured');
  }
  return rateLimit(config.requests, config.window);
}

/**
 * Check if request should bypass rate limiting
 */
function shouldBypass(req: Request): boolean {
  const bypass = rateLimitConfig.bypass;
  if (!bypass) return false;

  // Check admin bypass
  if (bypass.admin && (req as any).user?.isAdmin) {
    return true;
  }

  // Check internal bypass (localhost)
  if (bypass.internal && (req.ip === '127.0.0.1' || req.ip === '::1')) {
    return true;
  }

  // Check API key bypass
  if (bypass.apiKeys) {
    const apiKey = req.headers['x-api-key'] as string;
    if (apiKey && bypass.apiKeys.includes(apiKey)) {
      return true;
    }
  }

  return false;
}

/**
 * Get identifier for rate limiting
 */
function getIdentifier(req: Request): string {
  // Prefer user ID if authenticated
  if ((req as any).user?.id) {
    return \`user:\${(req as any).user.id}\`;
  }

  // Use API key if present
  const apiKey = req.headers['x-api-key'] as string;
  if (apiKey) {
    return \`apikey:\${apiKey}\`;
  }

  // Fall back to IP address
  return \`ip:\${req.ip}\`;
}
`;

    return {
      path: path.join(outputDirectory, 'api/src/middleware/rate-limit.ts'),
      content,
    };
  }

  private generateRedisStore(rateLimiting: RateLimiting, outputDirectory: string): GeneratedFile {
    const content = `/**
 * Redis Rate Limit Store
 * Generated by Bangstack
 */

import Redis from 'ioredis';
import { rateLimitConfig } from '../config/rate-limiting';

export class RateLimitStore {
  private redis: Redis;

  constructor() {
    this.redis = new Redis(rateLimitConfig.storage.redisUrl || process.env.REDIS_URL || 'redis://localhost:6379');

    this.redis.on('error', (error) => {
      console.error('Redis connection error:', error);
    });
  }

  /**
   * Increment counter for identifier
   */
  async increment(key: string, ttl: number): Promise<number> {
    const count = await this.redis.incr(key);

    // Set expiry on first increment
    if (count === 1) {
      await this.redis.expire(key, ttl);
    }

    return count;
  }

  /**
   * Get current count for identifier
   */
  async get(key: string): Promise<number> {
    const count = await this.redis.get(key);
    return count ? parseInt(count, 10) : 0;
  }

  /**
   * Get TTL for key
   */
  async ttl(key: string): Promise<number> {
    return await this.redis.ttl(key);
  }

  /**
   * Set value with expiry
   */
  async set(key: string, value: number, ttl: number): Promise<void> {
    await this.redis.setex(key, ttl, value);
  }

  /**
   * Delete key
   */
  async delete(key: string): Promise<void> {
    await this.redis.del(key);
  }

  /**
   * Get multiple keys matching pattern
   */
  async keys(pattern: string): Promise<string[]> {
    return await this.redis.keys(pattern);
  }

  /**
   * Close connection
   */
  async close(): Promise<void> {
    await this.redis.quit();
  }
}
`;

    return {
      path: path.join(outputDirectory, 'api/src/middleware/rate-limit-store.ts'),
      content,
    };
  }

  private generateMemoryStore(_rateLimiting: RateLimiting, outputDirectory: string): GeneratedFile {
    const content = `/**
 * In-Memory Rate Limit Store
 * Generated by Bangstack
 */

interface StoreEntry {
  count: number;
  expiresAt: number;
}

export class RateLimitStore {
  private store: Map<string, StoreEntry> = new Map();
  private cleanupInterval: NodeJS.Timeout;

  constructor() {
    // Clean up expired entries every minute
    this.cleanupInterval = setInterval(() => {
      this.cleanup();
    }, 60000);
  }

  /**
   * Increment counter for identifier
   */
  async increment(key: string, ttl: number): Promise<number> {
    const now = Date.now();
    const entry = this.store.get(key);

    if (!entry || entry.expiresAt < now) {
      this.store.set(key, {
        count: 1,
        expiresAt: now + ttl * 1000,
      });
      return 1;
    }

    entry.count++;
    return entry.count;
  }

  /**
   * Get current count for identifier
   */
  async get(key: string): Promise<number> {
    const now = Date.now();
    const entry = this.store.get(key);

    if (!entry || entry.expiresAt < now) {
      return 0;
    }

    return entry.count;
  }

  /**
   * Get TTL for key (in seconds)
   */
  async ttl(key: string): Promise<number> {
    const now = Date.now();
    const entry = this.store.get(key);

    if (!entry || entry.expiresAt < now) {
      return -1;
    }

    return Math.ceil((entry.expiresAt - now) / 1000);
  }

  /**
   * Set value with expiry
   */
  async set(key: string, value: number, ttl: number): Promise<void> {
    this.store.set(key, {
      count: value,
      expiresAt: Date.now() + ttl * 1000,
    });
  }

  /**
   * Delete key
   */
  async delete(key: string): Promise<void> {
    this.store.delete(key);
  }

  /**
   * Get multiple keys matching pattern
   */
  async keys(pattern: string): Promise<string[]> {
    const regex = new RegExp(pattern.replace('*', '.*'));
    return Array.from(this.store.keys()).filter(key => regex.test(key));
  }

  /**
   * Clean up expired entries
   */
  private cleanup(): void {
    const now = Date.now();
    for (const [key, entry] of this.store.entries()) {
      if (entry.expiresAt < now) {
        this.store.delete(key);
      }
    }
  }

  /**
   * Close store (cleanup)
   */
  async close(): Promise<void> {
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
    }
    this.store.clear();
  }
}
`;

    return {
      path: path.join(outputDirectory, 'api/src/middleware/rate-limit-store.ts'),
      content,
    };
  }

  private generateRateLimitStrategies(_rateLimiting: RateLimiting, outputDirectory: string): GeneratedFile {
    const content = `/**
 * Rate Limit Strategies
 * Generated by Bangstack
 */

import { RateLimitStore } from './rate-limit-store';

export interface RateLimitResult {
  allowed: boolean;
  limit: number;
  remaining: number;
  reset: number;
  retryAfter?: number;
}

export interface RateLimitStrategy {
  checkLimit(identifier: string, limit: number, window: string): Promise<RateLimitResult>;
}

/**
 * Parse window string to seconds
 */
function parseWindow(window: string): number {
  const match = window.match(/^(\\d+)([smhd])$/);
  if (!match) {
    throw new Error(\`Invalid window format: \${window}\`);
  }

  const value = parseInt(match[1], 10);
  const unit = match[2];

  switch (unit) {
    case 's': return value;
    case 'm': return value * 60;
    case 'h': return value * 3600;
    case 'd': return value * 86400;
    default: return value;
  }
}

/**
 * Sliding Window Strategy
 */
export class SlidingWindowStrategy implements RateLimitStrategy {
  constructor(private store: RateLimitStore) {}

  async checkLimit(identifier: string, limit: number, window: string): Promise<RateLimitResult> {
    const windowSeconds = parseWindow(window);
    const key = \`ratelimit:\${identifier}:\${window}\`;

    const count = await this.store.increment(key, windowSeconds);
    const ttl = await this.store.ttl(key);

    const remaining = Math.max(0, limit - count);
    const reset = Math.floor(Date.now() / 1000) + ttl;

    return {
      allowed: count <= limit,
      limit,
      remaining,
      reset,
      retryAfter: count > limit ? ttl : undefined,
    };
  }
}

/**
 * Fixed Window Strategy
 */
export class FixedWindowStrategy implements RateLimitStrategy {
  constructor(private store: RateLimitStore) {}

  async checkLimit(identifier: string, limit: number, window: string): Promise<RateLimitResult> {
    const windowSeconds = parseWindow(window);
    const now = Date.now();
    const windowStart = Math.floor(now / (windowSeconds * 1000)) * windowSeconds;
    const key = \`ratelimit:\${identifier}:\${windowStart}\`;

    const count = await this.store.increment(key, windowSeconds);
    const remaining = Math.max(0, limit - count);
    const reset = windowStart + windowSeconds;

    return {
      allowed: count <= limit,
      limit,
      remaining,
      reset,
      retryAfter: count > limit ? reset - Math.floor(now / 1000) : undefined,
    };
  }
}

/**
 * Token Bucket Strategy
 */
export class TokenBucketStrategy implements RateLimitStrategy {
  constructor(private store: RateLimitStore) {}

  async checkLimit(identifier: string, limit: number, window: string): Promise<RateLimitResult> {
    const windowSeconds = parseWindow(window);
    const refillRate = limit / windowSeconds; // tokens per second
    const key = \`ratelimit:\${identifier}:bucket\`;
    const lastKey = \`ratelimit:\${identifier}:last\`;

    const now = Date.now() / 1000;
    const lastUpdate = parseFloat(await this.store.get(lastKey).then(String)) || now;
    const tokens = parseFloat(await this.store.get(key).then(String)) || limit;

    // Calculate new tokens
    const elapsed = now - lastUpdate;
    const newTokens = Math.min(limit, tokens + elapsed * refillRate);

    if (newTokens >= 1) {
      // Consume one token
      await this.store.set(key, newTokens - 1, windowSeconds);
      await this.store.set(lastKey, now, windowSeconds);

      return {
        allowed: true,
        limit,
        remaining: Math.floor(newTokens - 1),
        reset: Math.floor(now + (limit - newTokens + 1) / refillRate),
      };
    }

    // Not enough tokens
    const retryAfter = Math.ceil((1 - newTokens) / refillRate);

    return {
      allowed: false,
      limit,
      remaining: 0,
      reset: Math.floor(now + retryAfter),
      retryAfter,
    };
  }
}
`;

    return {
      path: path.join(outputDirectory, 'api/src/middleware/rate-limit-strategies.ts'),
      content,
    };
  }

  private generateRateLimitTypes(_rateLimiting: RateLimiting, outputDirectory: string): GeneratedFile {
    const content = `/**
 * Rate Limiting Types
 * Generated by Bangstack
 */

export interface RateLimitConfig {
  requests: number;
  window: string;
  burst?: number;
}

export interface RateLimitInfo {
  limit: number;
  remaining: number;
  reset: number;
}

export interface RateLimitError extends Error {
  statusCode: number;
  retryAfter: number;
}
`;

    return {
      path: path.join(outputDirectory, 'api/src/types/rate-limit.ts'),
      content,
    };
  }
}

export const enhancedRateLimitingGenerator = new EnhancedRateLimitingGenerator();
