/**
 * Agent Result Cache
 *
 * Implements Redis-based caching for agent results to achieve
 * 80%+ cache hit rate and reduce redundant agent executions.
 *
 * Features:
 * - Cache key: agent_type + task hash
 * - 1-hour TTL on cached results
 * - Prometheus metrics for cache hit/miss tracking
 * - Automatic cache invalidation
 * - Compression for large results
 */

import crypto from 'crypto';
import { Cluster } from 'ioredis';
import { register, Counter, Histogram } from 'prom-client';

export interface CacheConfig {
  redisCluster: Cluster;
  ttl?: number; // Time to live in seconds (default: 3600 = 1 hour)
  namespace?: string; // Cache key namespace
  compressionThreshold?: number; // Compress results larger than this (bytes)
}

export interface CachedResult {
  agentType: string;
  taskHash: string;
  result: any;
  confidence: number;
  timestamp: number;
  executionTime: number;
}

export class ResultCache {
  private redis: Cluster;
  private ttl: number;
  private namespace: string;
  private compressionThreshold: number;

  // Prometheus metrics
  private cacheHitCounter: Counter;
  private cacheMissCounter: Counter;
  private cacheGetDuration: Histogram;
  private cacheSetDuration: Histogram;

  constructor(config: CacheConfig) {
    this.redis = config.redisCluster;
    this.ttl = config.ttl || 3600; // 1 hour default
    this.namespace = config.namespace || 'cfn:agent:result';
    this.compressionThreshold = config.compressionThreshold || 10240; // 10KB

    // Initialize Prometheus metrics
    this.cacheHitCounter = new Counter({
      name: 'cfn_agent_cache_hits_total',
      help: 'Total number of agent result cache hits',
      labelNames: ['agent_type'],
      registers: [register],
    });

    this.cacheMissCounter = new Counter({
      name: 'cfn_agent_cache_misses_total',
      help: 'Total number of agent result cache misses',
      labelNames: ['agent_type'],
      registers: [register],
    });

    this.cacheGetDuration = new Histogram({
      name: 'cfn_agent_cache_get_duration_seconds',
      help: 'Duration of cache get operations',
      labelNames: ['agent_type', 'hit'],
      buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1],
      registers: [register],
    });

    this.cacheSetDuration = new Histogram({
      name: 'cfn_agent_cache_set_duration_seconds',
      help: 'Duration of cache set operations',
      labelNames: ['agent_type'],
      buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1],
      registers: [register],
    });
  }

  /**
   * Generate cache key from agent type and task
   */
  private generateCacheKey(agentType: string, task: string): string {
    const taskHash = this.hashTask(task);
    return `${this.namespace}:${agentType}:${taskHash}`;
  }

  /**
   * Hash task description for cache key
   */
  private hashTask(task: string): string {
    return crypto.createHash('sha256').update(task).digest('hex').substring(0, 16);
  }

  /**
   * Compress data if it exceeds threshold
   */
  private async compress(data: string): Promise<string> {
    if (data.length < this.compressionThreshold) {
      return data;
    }

    // Simple base64 encoding for compression simulation
    // In production, use zlib or similar
    return Buffer.from(data).toString('base64');
  }

  /**
   * Decompress data
   */
  private async decompress(data: string): Promise<string> {
    try {
      return Buffer.from(data, 'base64').toString('utf-8');
    } catch {
      return data; // Not compressed
    }
  }

  /**
   * Get cached result
   */
  async get(
    agentType: string,
    task: string
  ): Promise<CachedResult | null> {
    const startTime = Date.now();
    const cacheKey = this.generateCacheKey(agentType, task);

    try {
      const cached = await this.redis.get(cacheKey);

      const duration = (Date.now() - startTime) / 1000;

      if (cached) {
        this.cacheHitCounter.inc({ agent_type: agentType });
        this.cacheGetDuration.observe(
          { agent_type: agentType, hit: 'true' },
          duration
        );

        const decompressed = await this.decompress(cached);
        const result = JSON.parse(decompressed);

        console.log(
          `Cache HIT: ${agentType} (${this.hashTask(task).substring(0, 8)})`
        );

        return result;
      } else {
        this.cacheMissCounter.inc({ agent_type: agentType });
        this.cacheGetDuration.observe(
          { agent_type: agentType, hit: 'false' },
          duration
        );

        console.log(
          `Cache MISS: ${agentType} (${this.hashTask(task).substring(0, 8)})`
        );

        return null;
      }
    } catch (err) {
      console.error('Error getting cached result:', err);
      return null;
    }
  }

  /**
   * Set cached result
   */
  async set(
    agentType: string,
    task: string,
    result: any,
    confidence: number,
    executionTime: number
  ): Promise<void> {
    const startTime = Date.now();
    const cacheKey = this.generateCacheKey(agentType, task);

    try {
      const cachedResult: CachedResult = {
        agentType,
        taskHash: this.hashTask(task),
        result,
        confidence,
        timestamp: Date.now(),
        executionTime,
      };

      const serialized = JSON.stringify(cachedResult);
      const compressed = await this.compress(serialized);

      await this.redis.setex(cacheKey, this.ttl, compressed);

      const duration = (Date.now() - startTime) / 1000;
      this.cacheSetDuration.observe({ agent_type: agentType }, duration);

      console.log(
        `Cache SET: ${agentType} (${this.hashTask(task).substring(0, 8)}) - TTL: ${this.ttl}s`
      );
    } catch (err) {
      console.error('Error setting cached result:', err);
    }
  }

  /**
   * Invalidate cached result
   */
  async invalidate(agentType: string, task: string): Promise<void> {
    const cacheKey = this.generateCacheKey(agentType, task);

    try {
      await this.redis.del(cacheKey);
      console.log(
        `Cache INVALIDATE: ${agentType} (${this.hashTask(task).substring(0, 8)})`
      );
    } catch (err) {
      console.error('Error invalidating cached result:', err);
    }
  }

  /**
   * Invalidate all cached results for an agent type
   */
  async invalidateAgentType(agentType: string): Promise<void> {
    const pattern = `${this.namespace}:${agentType}:*`;

    try {
      const keys = await this.redis.keys(pattern);

      if (keys.length > 0) {
        await this.redis.del(...keys);
        console.log(
          `Cache INVALIDATE ALL: ${agentType} (${keys.length} keys)`
        );
      }
    } catch (err) {
      console.error('Error invalidating agent type cache:', err);
    }
  }

  /**
   * Get cache statistics
   */
  async getStats(): Promise<{
    hits: number;
    misses: number;
    hitRate: number;
    totalKeys: number;
  }> {
    try {
      // Get metrics from Prometheus
      const metrics = await register.metrics();
      const lines = metrics.split('\n');

      let hits = 0;
      let misses = 0;

      for (const line of lines) {
        if (line.startsWith('cfn_agent_cache_hits_total')) {
          const match = line.match(/(\d+)$/);
          if (match) hits += parseInt(match[1]);
        } else if (line.startsWith('cfn_agent_cache_misses_total')) {
          const match = line.match(/(\d+)$/);
          if (match) misses += parseInt(match[1]);
        }
      }

      const total = hits + misses;
      const hitRate = total > 0 ? hits / total : 0;

      // Get total cached keys
      const pattern = `${this.namespace}:*`;
      const keys = await this.redis.keys(pattern);

      return {
        hits,
        misses,
        hitRate,
        totalKeys: keys.length,
      };
    } catch (err) {
      console.error('Error getting cache stats:', err);
      return { hits: 0, misses: 0, hitRate: 0, totalKeys: 0 };
    }
  }

  /**
   * Clear all cached results
   */
  async clear(): Promise<void> {
    const pattern = `${this.namespace}:*`;

    try {
      const keys = await this.redis.keys(pattern);

      if (keys.length > 0) {
        await this.redis.del(...keys);
        console.log(`Cache CLEAR: ${keys.length} keys deleted`);
      }
    } catch (err) {
      console.error('Error clearing cache:', err);
    }
  }

  /**
   * Warm up cache with common tasks
   */
  async warmUp(
    commonTasks: Array<{ agentType: string; task: string; result: any; confidence: number }>
  ): Promise<void> {
    console.log(`Cache WARM UP: ${commonTasks.length} tasks`);

    for (const task of commonTasks) {
      await this.set(
        task.agentType,
        task.task,
        task.result,
        task.confidence,
        0
      );
    }

    console.log('Cache warm up complete');
  }

  /**
   * Get cache hit rate by agent type
   */
  async getHitRateByAgentType(): Promise<Map<string, number>> {
    const hitRates = new Map<string, number>();

    try {
      const metrics = await register.metrics();
      const lines = metrics.split('\n');

      const hitsByType = new Map<string, number>();
      const missesByType = new Map<string, number>();

      for (const line of lines) {
        if (line.startsWith('cfn_agent_cache_hits_total')) {
          const typeMatch = line.match(/agent_type="([^"]+)"/);
          const countMatch = line.match(/(\d+)$/);
          if (typeMatch && countMatch) {
            const agentType = typeMatch[1];
            const count = parseInt(countMatch[1]);
            hitsByType.set(agentType, (hitsByType.get(agentType) || 0) + count);
          }
        } else if (line.startsWith('cfn_agent_cache_misses_total')) {
          const typeMatch = line.match(/agent_type="([^"]+)"/);
          const countMatch = line.match(/(\d+)$/);
          if (typeMatch && countMatch) {
            const agentType = typeMatch[1];
            const count = parseInt(countMatch[1]);
            missesByType.set(agentType, (missesByType.get(agentType) || 0) + count);
          }
        }
      }

      // Calculate hit rates
      const allTypes = new Set([...hitsByType.keys(), ...missesByType.keys()]);
      for (const agentType of allTypes) {
        const hits = hitsByType.get(agentType) || 0;
        const misses = missesByType.get(agentType) || 0;
        const total = hits + misses;
        const hitRate = total > 0 ? hits / total : 0;
        hitRates.set(agentType, hitRate);
      }

      return hitRates;
    } catch (err) {
      console.error('Error getting hit rate by agent type:', err);
      return hitRates;
    }
  }
}

// Singleton instance
let resultCacheInstance: ResultCache | null = null;

/**
 * Initialize singleton result cache
 */
export function initResultCache(config: CacheConfig): ResultCache {
  if (!resultCacheInstance) {
    resultCacheInstance = new ResultCache(config);
    console.log('Result cache initialized');
  }

  return resultCacheInstance;
}

/**
 * Get singleton result cache instance
 */
export function getResultCache(): ResultCache {
  if (!resultCacheInstance) {
    throw new Error(
      'Result cache not initialized. Call initResultCache first.'
    );
  }
  return resultCacheInstance;
}

/**
 * Clear and reset singleton result cache
 */
export async function resetResultCache(): Promise<void> {
  if (resultCacheInstance) {
    await resultCacheInstance.clear();
    resultCacheInstance = null;
  }
}
