/**
 * Copyright (c) 2024-2026 Тарабанов Александр Викторович
 * All rights reserved.
 * 
 * This software is proprietary and confidential.
 * Unauthorized copying, modification, or distribution is prohibited.
 * 
 * For licensing inquiries: info@hb3-accelerator.com
 * Website: https://hb3-accelerator.com
 * GitHub: https://github.com/VC-HB3-Accelerator
 */

/**
 * Кэширование AI ответов для ускорения работы
 * Использует настройки из aiConfigService
 */

const crypto = require('crypto');
const logger = require('../utils/logger');
const ollamaConfig = require('./ollamaConfig');
const aiConfigService = require('./aiConfigService');

class AICache {
  constructor() {
    // Загружаем настройки из aiConfigService
    this.cache = new Map();
    this._loadSettings();
  }

  /**
   * Загружает настройки кэша из aiConfigService
   * @private
   */
  async _loadSettings() {
    try {
      const cacheConfig = await aiConfigService.getCacheConfig();
      this.maxSize = cacheConfig.maxSize || 1000;
      this.ttl = cacheConfig.llmTTL || 86400000; // 24 часа
      this.ragTtl = cacheConfig.ragTTL || 300000; // 5 минут
    } catch (error) {
      logger.warn('[AICache] Ошибка загрузки настроек, используем дефолты:', error.message);
      // Дефолтные значения
      const timeouts = ollamaConfig.getTimeouts();
      this.maxSize = timeouts.cacheMax || 1000;
      this.ttl = timeouts.cacheLLM || 86400000;
      this.ragTtl = timeouts.cacheRAG || 300000;
    }
  }

  /**
   * Получает актуальные настройки (перезагружает из БД)
   */
  async _getSettings() {
    await this._loadSettings();
    return {
      maxSize: this.maxSize,
      ttl: this.ttl,
      ragTtl: this.ragTtl
    };
  }

  /**
   * Генерация ключа кэша на основе запроса
   * Использует параметры LLM из настроек для генерации ключа
   */
  async generateKey(messages, options = {}) {
    // Загружаем актуальные параметры LLM для ключа
    const llmParams = await aiConfigService.getLLMParameters();
    
    const content = JSON.stringify({
      messages: messages.map(m => ({ role: m.role, content: m.content })),
      temperature: options.temperature || llmParams.temperature,
      maxTokens: options.num_predict || llmParams.maxTokens
    });
    return crypto.createHash('md5').update(content).digest('hex');
  }

  /**
   * Генерация ключа для RAG результатов
   * Включает tagIds для учета фильтрации по тегам
   */
  generateKeyForRAG(tableId, userQuestion, product = null, userId = null, tagIds = null) {
    // Сортируем tagIds для стабильности ключа (одинаковый порядок = одинаковый ключ)
    const sortedTagIds = tagIds ? [...tagIds].sort((a, b) => a - b) : null;
    const content = JSON.stringify({ tableId, userQuestion, product, userId, tagIds: sortedTagIds });
    return crypto.createHash('md5').update(content).digest('hex');
  }

  /**
   * Получение ответа из кэша (LLM)
   */
  get(key) {
    const cached = this.cache.get(key);
    if (!cached) return null;

    // Проверяем TTL
    if (Date.now() - cached.timestamp > this.ttl) {
      this.cache.delete(key);
      return null;
    }

    logger.info(`[AICache] Cache hit for key: ${key.substring(0, 8)}...`);
    return cached.response;
  }

  /**
   * Получение с учетом типа кэша (RAG или LLM)
   */
  getWithTTL(key, type = 'llm') {
    const cached = this.cache.get(key);
    if (!cached) return null;

    const ttl = type === 'rag' ? this.ragTtl : this.ttl;

    // Проверяем TTL
    if (Date.now() - cached.timestamp > ttl) {
      this.cache.delete(key);
      return null;
    }

    logger.info(`[AICache] Cache hit (${type}) for key: ${key.substring(0, 8)}...`);
    return cached.response;
  }

  /**
   * Сохранение в кэш
   */
  set(key, value, type = 'llm') {
    // Проверяем размер кэша
    if (this.cache.size >= this.maxSize) {
      // Удаляем самую старую запись
      const oldestKey = Array.from(this.cache.keys())[0];
      this.cache.delete(oldestKey);
      logger.warn(`[AICache] Кэш переполнен, удалена старая запись: ${oldestKey.substring(0, 8)}...`);
    }

    this.cache.set(key, {
      response: value,
      timestamp: Date.now(),
      type
    });

    logger.debug(`[AICache] Сохранено в кэш (${type}): ${key.substring(0, 8)}...`);
  }

  /**
   * Сохранение с указанием типа
   */
  setWithType(key, value, type = 'llm') {
    this.set(key, value, type);
  }

  /**
   * Очистка кэша
   */
  clear() {
    const size = this.cache.size;
    this.cache.clear();
    logger.info(`[AICache] Кэш очищен. Удалено записей: ${size}`);
    return size;
  }

  /**
   * Получение статистики
   */
  getStats() {
    const stats = {
      size: this.cache.size,
      maxSize: this.maxSize,
      ttl: this.ttl,
      ragTtl: this.ragTtl
    };

    // Подсчитываем по типам
    let llmCount = 0;
    let ragCount = 0;
    for (const [key, value] of this.cache.entries()) {
      if (value.type === 'rag') {
        ragCount++;
      } else {
        llmCount++;
      }
    }

    stats.llmCount = llmCount;
    stats.ragCount = ragCount;

    return stats;
  }

  /**
   * Получение статистики по типам
   */
  getStatsByType() {
    const stats = {
      llm: { count: 0, size: 0 },
      rag: { count: 0, size: 0 }
    };

    for (const [key, value] of this.cache.entries()) {
      const type = value.type || 'llm';
      stats[type].count++;
      stats[type].size += JSON.stringify(value.response).length;
    }

    return stats;
  }

  /**
   * Инвалидация кэша по префиксу
   */
  invalidateByPrefix(prefix) {
    let count = 0;
    for (const key of this.cache.keys()) {
      if (key.startsWith(prefix)) {
        this.cache.delete(key);
        count++;
      }
    }
    if (count > 0) {
      logger.info(`[AICache] Инвалидировано записей с префиксом ${prefix}: ${count}`);
    }
    return count;
  }
}

// Экспортируем singleton экземпляр
const aiCache = new AICache();

module.exports = aiCache;

