/**
 * Research Cache with File-Based Storage
 *
 * @module planning/seo/lib/research-cache
 * @description File-based cache with TTL support for research results
 * Note: RuVector integration deferred to Phase 5
 */

import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import {
  CacheEntry,
  ResearchQuery,
  ResearchResult,
  ResearchError,
  ResearchErrorCode,
  CacheStats,
} from '../types/research';

/**
 * Default cache configuration
 */
const DEFAULT_CONFIG = {
  cacheDir: path.join(process.env.HOME || '/tmp', '.cfn/seo/cache/research'),
  defaultTtl: {
    serp: 86400, // 24 hours for SERP data
    content: 604800, // 7 days for content data
    hybrid: 86400, // 24 hours for hybrid queries
  },
  maxCacheSize: 1024 * 1024 * 100, // 100MB
  compressionEnabled: false, // Defer compression to future optimization
};

/**
 * Research cache implementation with file-based storage
 */
export class ResearchCache {
  private cacheDir: string;
  private stats: {
    hits: number;
    misses: number;
    writes: number;
    evictions: number;
  };

  constructor(cacheDir?: string) {
    this.cacheDir = cacheDir || DEFAULT_CONFIG.cacheDir;
    this.stats = {
      hits: 0,
      misses: 0,
      writes: 0,
      evictions: 0,
    };

    this.ensureCacheDir();
  }

  /**
   * Ensure cache directory exists
   */
  private ensureCacheDir(): void {
    if (!fs.existsSync(this.cacheDir)) {
      fs.mkdirSync(this.cacheDir, { recursive: true });
    }
  }

  /**
   * Generate cache key from query
   *
   * @param query - Research query
   * @returns Cache key (SHA-256 hash)
   */
  generateCacheKey(query: ResearchQuery): string {
    const keyData = {
      query: query.query,
      type: query.type,
      options: {
        maxResults: query.options?.maxResults,
        targetUrl: query.options?.targetUrl,
        deepCrawl: query.options?.deepCrawl,
      },
    };

    const keyString = JSON.stringify(keyData);
    return crypto.createHash('sha256').update(keyString).digest('hex');
  }

  /**
   * Get cached result
   *
   * @param query - Research query
   * @returns Cached result or null if not found/expired
   */
  async get(query: ResearchQuery): Promise<ResearchResult | null> {
    const cacheKey = this.generateCacheKey(query);
    const cacheFile = path.join(this.cacheDir, `${cacheKey}.json`);

    try {
      if (!fs.existsSync(cacheFile)) {
        this.stats.misses += 1;
        return null;
      }

      const cacheData = fs.readFileSync(cacheFile, 'utf-8');
      const entry: CacheEntry<ResearchResult> = JSON.parse(cacheData);

      // Check expiration
      const now = new Date();
      const expiresAt = new Date(entry.expiresAt);

      if (now > expiresAt) {
        // Expired, delete cache file
        fs.unlinkSync(cacheFile);
        this.stats.misses += 1;
        return null;
      }

      // Update access tracking
      entry.accessCount += 1;
      entry.lastAccessedAt = now;
      fs.writeFileSync(cacheFile, JSON.stringify(entry, null, 2));

      this.stats.hits += 1;

      // Return cached data with updated metadata
      const result = entry.data;
      result.metadata.fromCache = true;
      result.metadata.cacheKey = cacheKey;

      return result;
    } catch (error) {
      // Cache read error, treat as miss
      this.stats.misses += 1;
      return null;
    }
  }

  /**
   * Set cache entry
   *
   * @param query - Research query
   * @param result - Research result to cache
   * @returns Cache key
   */
  async set(query: ResearchQuery, result: ResearchResult): Promise<string> {
    const cacheKey = this.generateCacheKey(query);
    const cacheFile = path.join(this.cacheDir, `${cacheKey}.json`);

    try {
      // Determine TTL based on query type and custom options
      const ttl =
        query.options?.cacheTtl ||
        DEFAULT_CONFIG.defaultTtl[query.type] ||
        DEFAULT_CONFIG.defaultTtl.hybrid;

      const now = new Date();
      const expiresAt = new Date(now.getTime() + ttl * 1000);

      const entry: CacheEntry<ResearchResult> = {
        key: cacheKey,
        data: result,
        createdAt: now,
        expiresAt,
        accessCount: 0,
        lastAccessedAt: now,
        metadata: {
          queryHash: this.hashQuery(query.query),
          resultType: query.type,
          resultCount:
            (result.serpResults?.length || 0) + (result.contentResults?.length || 0),
        },
      };

      fs.writeFileSync(cacheFile, JSON.stringify(entry, null, 2));
      this.stats.writes += 1;

      // Check cache size and evict if needed
      await this.evictIfNeeded();

      return cacheKey;
    } catch (error) {
      throw new ResearchError(
        `Failed to write cache entry: ${error instanceof Error ? error.message : 'Unknown error'}`,
        ResearchErrorCode.CACHE_ERROR,
        { cacheKey, error }
      );
    }
  }

  /**
   * Invalidate cache entry
   *
   * @param query - Research query to invalidate
   * @returns True if entry was deleted
   */
  async invalidate(query: ResearchQuery): Promise<boolean> {
    const cacheKey = this.generateCacheKey(query);
    const cacheFile = path.join(this.cacheDir, `${cacheKey}.json`);

    try {
      if (fs.existsSync(cacheFile)) {
        fs.unlinkSync(cacheFile);
        return true;
      }
      return false;
    } catch (error) {
      throw new ResearchError(
        `Failed to invalidate cache entry: ${error instanceof Error ? error.message : 'Unknown error'}`,
        ResearchErrorCode.CACHE_ERROR,
        { cacheKey, error }
      );
    }
  }

  /**
   * Invalidate all cache entries matching a pattern
   *
   * @param queryPattern - Query text pattern (substring match)
   * @returns Number of entries invalidated
   */
  async invalidateByPattern(queryPattern: string): Promise<number> {
    let invalidatedCount = 0;

    try {
      const cacheFiles = fs.readdirSync(this.cacheDir);

      for (const file of cacheFiles) {
        if (!file.endsWith('.json')) continue;

        const filePath = path.join(this.cacheDir, file);
        const cacheData = fs.readFileSync(filePath, 'utf-8');
        const entry: CacheEntry<ResearchResult> = JSON.parse(cacheData);

        if (entry.data.query.query.includes(queryPattern)) {
          fs.unlinkSync(filePath);
          invalidatedCount += 1;
        }
      }

      return invalidatedCount;
    } catch (error) {
      throw new ResearchError(
        `Failed to invalidate by pattern: ${error instanceof Error ? error.message : 'Unknown error'}`,
        ResearchErrorCode.CACHE_ERROR,
        { queryPattern, error }
      );
    }
  }

  /**
   * Clear all cache entries
   */
  async clear(): Promise<void> {
    try {
      const cacheFiles = fs.readdirSync(this.cacheDir);

      for (const file of cacheFiles) {
        if (file.endsWith('.json')) {
          fs.unlinkSync(path.join(this.cacheDir, file));
        }
      }

      this.stats = {
        hits: 0,
        misses: 0,
        writes: 0,
        evictions: 0,
      };
    } catch (error) {
      throw new ResearchError(
        `Failed to clear cache: ${error instanceof Error ? error.message : 'Unknown error'}`,
        ResearchErrorCode.CACHE_ERROR,
        { error }
      );
    }
  }

  /**
   * Evict expired or excess entries if cache size exceeds limit
   */
  private async evictIfNeeded(): Promise<void> {
    const cacheSize = this.getCacheSize();

    if (cacheSize <= DEFAULT_CONFIG.maxCacheSize) {
      return;
    }

    try {
      const cacheFiles = fs.readdirSync(this.cacheDir);
      const entries: Array<{ file: string; accessedAt: Date; size: number }> = [];

      // Build entry list with metadata
      for (const file of cacheFiles) {
        if (!file.endsWith('.json')) continue;

        const filePath = path.join(this.cacheDir, file);
        const stats = fs.statSync(filePath);
        const cacheData = fs.readFileSync(filePath, 'utf-8');
        const entry: CacheEntry<ResearchResult> = JSON.parse(cacheData);

        entries.push({
          file,
          accessedAt: new Date(entry.lastAccessedAt),
          size: stats.size,
        });
      }

      // Sort by least recently accessed
      entries.sort((a, b) => a.accessedAt.getTime() - b.accessedAt.getTime());

      // Evict oldest entries until cache size is acceptable
      let currentSize = cacheSize;
      const targetSize = DEFAULT_CONFIG.maxCacheSize * 0.8; // Evict to 80% capacity

      for (const entry of entries) {
        if (currentSize <= targetSize) break;

        fs.unlinkSync(path.join(this.cacheDir, entry.file));
        currentSize -= entry.size;
        this.stats.evictions += 1;
      }
    } catch (error) {
      // Non-fatal eviction error
      console.error('Cache eviction error:', error);
    }
  }

  /**
   * Get total cache size in bytes
   */
  private getCacheSize(): number {
    try {
      const cacheFiles = fs.readdirSync(this.cacheDir);
      let totalSize = 0;

      for (const file of cacheFiles) {
        if (file.endsWith('.json')) {
          const filePath = path.join(this.cacheDir, file);
          const stats = fs.statSync(filePath);
          totalSize += stats.size;
        }
      }

      return totalSize;
    } catch (error) {
      return 0;
    }
  }

  /**
   * Get cache statistics
   */
  getStats(): CacheStats {
    const totalEntries = this.getCacheEntryCount();
    const sizeBytes = this.getCacheSize();
    const totalRequests = this.stats.hits + this.stats.misses;
    const hitRate = totalRequests > 0 ? this.stats.hits / totalRequests : 0;

    // Calculate oldest entry age
    let oldestEntryAge: number | undefined;
    let totalAccessCount = 0;

    try {
      const cacheFiles = fs.readdirSync(this.cacheDir);

      for (const file of cacheFiles) {
        if (!file.endsWith('.json')) continue;

        const filePath = path.join(this.cacheDir, file);
        const cacheData = fs.readFileSync(filePath, 'utf-8');
        const entry: CacheEntry<ResearchResult> = JSON.parse(cacheData);

        const age = (Date.now() - new Date(entry.createdAt).getTime()) / 1000;
        if (oldestEntryAge === undefined || age > oldestEntryAge) {
          oldestEntryAge = age;
        }

        totalAccessCount += entry.accessCount;
      }
    } catch (error) {
      // Non-fatal stats error
    }

    const avgAccessCount = totalEntries > 0 ? totalAccessCount / totalEntries : undefined;

    return {
      hits: this.stats.hits,
      misses: this.stats.misses,
      hitRate,
      totalEntries,
      sizeBytes,
      oldestEntryAge,
      avgAccessCount,
    };
  }

  /**
   * Get cache entry count
   */
  private getCacheEntryCount(): number {
    try {
      const cacheFiles = fs.readdirSync(this.cacheDir);
      return cacheFiles.filter((file) => file.endsWith('.json')).length;
    } catch (error) {
      return 0;
    }
  }

  /**
   * Hash query string for metadata
   */
  private hashQuery(query: string): string {
    return crypto.createHash('md5').update(query).digest('hex');
  }
}

/**
 * Default cache instance
 */
export const researchCache = new ResearchCache();
