/**
 * 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
 */

const encryptedDb = require('./encryptedDatabaseService');
const vectorSearch = require('./vectorSearchClient');
const { getProviderSettings } = require('./aiProviderSettingsService');
const axios = require('axios');
const ollamaConfig = require('./ollamaConfig');
const aiCache = require('./ai-cache');
const AIQueue = require('./ai-queue');
const aiConfigService = require('./aiConfigService');
const userContextService = require('./userContextService');
const profileAnalysisService = require('./profileAnalysisService');
const { buildOllamaRequest } = require('../utils/ollamaRequestBuilder');
const logger = require('../utils/logger');
const db = require('../db');

// Кэш для плейсхолдеров таблиц
const tablePlaceholdersCache = {
  data: null,
  timestamp: 0
};
const TABLE_PLACEHOLDERS_CACHE_TTL = 10 * 60 * 1000; // 10 минут

/**
 * Генерация плейсхолдера из названия (транслитерация)
 * @param {string} name - Название таблицы или столбца
 * @returns {string} Плейсхолдер
 */
function generatePlaceholder(name) {
  if (!name || typeof name !== 'string') {
    return 'placeholder';
  }

  // Транслитерация (упрощённая)
  const cyrillicToLatinMap = {
    а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'e', ж: 'zh', з: 'z', 
    и: 'i', й: 'y', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', 
    с: 's', т: 't', у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', 
    щ: 'sch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya',
    А: 'A', Б: 'B', В: 'V', Г: 'G', Д: 'D', Е: 'E', Ё: 'E', Ж: 'Zh', З: 'Z',
    И: 'I', Й: 'Y', К: 'K', Л: 'L', М: 'M', Н: 'N', О: 'O', П: 'P', Р: 'R',
    С: 'S', Т: 'T', У: 'U', Ф: 'F', Х: 'H', Ц: 'Ts', Ч: 'Ch', Ш: 'Sh',
    Щ: 'Sch', Ъ: '', Ы: 'Y', Ь: '', Э: 'E', Ю: 'Yu', Я: 'Ya'
  };

  let translit = name.toLowerCase().split('').map(ch => {
    if (cyrillicToLatinMap[ch]) return cyrillicToLatinMap[ch];
    if (/[a-z0-9]/.test(ch)) return ch;
    if (ch === ' ') return '_';
    if (ch === '-') return '_';
    return '';
  }).join('');

  // Удаляем множественные подчеркивания и подчеркивания в начале/конце
  translit = translit.replace(/_+/g, '_').replace(/^_+|_+$/g, '');

  // Если translit пустой, используем fallback
  if (!translit) {
    translit = 'placeholder';
  }

  return translit;
}

// Управляет поведением: выполнять ли upsert всех строк на каждый запрос поиска
// Теперь из настроек ai_config
let RAG_BEHAVIOR = null;

// Флаги для включения/выключения Queue и Cache
const USE_AI_CACHE = process.env.USE_AI_CACHE !== 'false'; // default: true
const USE_AI_QUEUE = process.env.USE_AI_QUEUE !== 'false'; // default: true

// Создаем экземпляр очереди
const aiQueue = new AIQueue();

// Загружаем RAG поведение из настроек
async function getRAGBehavior() {
  if (!RAG_BEHAVIOR) {
    RAG_BEHAVIOR = await aiConfigService.getRAGBehavior();
  }
  return RAG_BEHAVIOR;
}

async function getTableData(tableId) {
  const columns = await encryptedDb.getData('user_columns', { table_id: tableId });
  const rows = await encryptedDb.getData('user_rows', { table_id: tableId });
  
  const cellValues = rows.length > 0 
    ? await encryptedDb.getData('user_cell_values', { row_id: { $in: rows.map(row => row.id) } })
    : [];

  const getColId = purpose => columns.find(col => col.options?.purpose === purpose)?.id;
  const questionColId = getColId('question');
  const answerColId = getColId('answer');
  const contextColId = getColId('context');
  const productColId = getColId('product');
  const priorityColId = getColId('priority');
  const dateColId = getColId('date');
  
  const data = rows.map(row => {
    const cells = cellValues.filter(cell => cell.row_id === row.id);
    const result = {
      id: row.id,
      question: cells.find(c => c.column_id === questionColId)?.value,
      answer: cells.find(c => c.column_id === answerColId)?.value,
      context: cells.find(c => c.column_id === contextColId)?.value,
      product: parseIfArray(cells.find(c => c.column_id === productColId)?.value),
      userTags: parseIfArray(cells.find(c => c.column_id === getColId('userTags'))?.value),
      priority: cells.find(c => c.column_id === priorityColId)?.value,
      date: cells.find(c => c.column_id === dateColId)?.value,
    };
    return result;
  });
  return data;
}

/**
 * Получить строки таблицы с фильтрацией по тегам пользователя
 * @param {number} tableId - ID таблицы
 * @param {number} userId - ID пользователя (опционально)
 * @returns {Promise<Array<number>>} Массив rowIds отфильтрованных строк
 */
async function getFilteredRowIdsByTags(tableId, userId = null) {
  if (!userId) {
    return null; // Без фильтрации
  }

  try {
    // Получаем теги пользователя
    const tagIds = await userContextService.getUserTags(userId);
    if (!tagIds || tagIds.length === 0) {
      return null; // Нет тегов - без фильтрации
    }

    // Получаем столбцы таблицы
    const columns = await encryptedDb.getData('user_columns', { table_id: tableId });
    
    // Находим столбец "Теги" с purpose='userTags' и типом multiselect-relation
    const tagsColumn = columns.find(col => 
      col.options?.purpose === 'userTags' && 
      (col.type === 'multiselect-relation' || col.type === 'relation')
    );

    if (!tagsColumn) {
      // Нет столбца с тегами - без фильтрации
      return null;
    }

    // Фильтруем строки по тегам через user_table_relations
    // Логика: найти строки, где хотя бы один тег пользователя совпадает с тегами строки
    const query = db.getQuery();
    const result = await query(`
      SELECT DISTINCT from_row_id
      FROM user_table_relations
      WHERE column_id = $1
        AND to_row_id = ANY($2)
    `, [tagsColumn.id, tagIds]);

    const filteredRowIds = result.rows.map(row => row.from_row_id);
    
    if (filteredRowIds.length === 0) {
      // Нет строк с тегами пользователя - возвращаем пустой массив
      return [];
    }

    console.log(`[RAG] Фильтрация по тегам: найдено ${filteredRowIds.length} строк с тегами пользователя`);
    return filteredRowIds;
  } catch (error) {
    logger.error('[RAG] Ошибка фильтрации по тегам:', error.message);
    return null; // При ошибке - без фильтрации
  }
}

async function ragAnswer({ tableId, userQuestion, product = null, threshold = null, forceReindex = false, userId = null }) {
  // Загружаем настройки RAG из ai_config
  const ragConfig = await aiConfigService.getRAGConfig();
  const ragBehavior = await getRAGBehavior();
  
  // Используем настройки из БД, если не переданы явно
  const finalThreshold = threshold !== null ? threshold : ragConfig.threshold;
  const maxResults = ragConfig.maxResults || 3;
  const upsertOnQuery = ragBehavior.upsertOnQuery || false;
  
  // Получаем теги пользователя для включения в ключ кэша
  let userTagIds = null;
  if (userId) {
    userTagIds = await userContextService.getUserTags(userId);
    // Если тегов нет, сохраняем null (не пустой массив, чтобы различать "нет тегов" и "не проверяли")
    if (userTagIds && userTagIds.length === 0) {
      userTagIds = null;
    }
  }
  
  // Проверяем кэш (используем ai-cache вместо ragCache)
  // Включаем tagIds в ключ кэша для учета фильтрации по тегам
  if (USE_AI_CACHE) {
    const cacheKey = aiCache.generateKeyForRAG(tableId, userQuestion, product, userId, userTagIds);
    const cached = aiCache.getWithTTL(cacheKey, 'rag');
    if (cached) {
      console.log(`[RAG] Возврат RAG результата из кэша (userId=${userId}, tagIds=${userTagIds ? userTagIds.join(',') : 'null'})`);
      return cached;
    }
  }
  
  // Фильтрация по тегам пользователя ДО получения данных
  const filteredRowIds = await getFilteredRowIdsByTags(tableId, userId);
  
  const data = await getTableData(tableId);
  
  // Применяем фильтрацию по тегам, если есть отфильтрованные строки
  let filteredData = data;
  if (filteredRowIds !== null) {
    if (filteredRowIds.length === 0) {
      // Нет строк с тегами пользователя - возвращаем пустой результат
      console.log(`[RAG] Нет строк с тегами пользователя`);
      return {
        answer: null,
        context: null,
        product: null,
        priority: null,
        date: null,
        score: null
      };
    }
    // Фильтруем данные по rowIds
    filteredData = data.filter(row => filteredRowIds.includes(row.id));
    console.log(`[RAG] Фильтрация по тегам: ${data.length} -> ${filteredData.length} строк`);
  }
  
  const questions = filteredData.map(row => row.question && typeof row.question === 'string' ? row.question.trim() : row.question);
  // Фильтруем только строки с непустым вопросом (text)
  const rowsForUpsert = filteredData
    .filter(row => row.id && row.question && String(row.question).trim().length > 0)
    .map(row => ({
      row_id: row.id,
      text: row.question,
      metadata: {
        answer: row.answer || null,
        context: row.context || null,
        product: row.product || [],
        userTags: row.userTags || [],
        priority: row.priority || null,
        date: row.date || null
      }
    }));
  
  // Выполняем upsert ТОЛЬКО если явно разрешено настройками или параметром
  if ((upsertOnQuery || forceReindex) && rowsForUpsert.length > 0) {
    await vectorSearch.upsert(tableId, rowsForUpsert);
  }
  
  // Поиск
  let results = [];
  if (rowsForUpsert.length > 0 && userQuestion && userQuestion.trim()) {
    results = await vectorSearch.search(tableId, userQuestion, maxResults);
    console.log(`[RAG] Search completed, got ${results.length} results`);
    
    results.forEach((result, index) => {
      console.log(`[RAG] Search result ${index}:`, {
        row_id: result.row_id,
        score: result.score,
        metadata: result.metadata
      });
    });
  } else {
    console.log(`[RAG] No data in table, skipping search`);
  }
  
  // Фильтрация по тегам пользователя (если была применена)
  // Если мы уже отфильтровали данные по тегам, нужно отфильтровать результаты векторного поиска
  let filtered = results;
  
  if (filteredRowIds !== null && filteredRowIds.length > 0) {
    // Фильтруем результаты векторного поиска по отфильтрованным rowIds
    filtered = filtered.filter(result => filteredRowIds.includes(Number(result.row_id)));
    console.log(`[RAG] Фильтрация результатов векторного поиска по тегам: ${results.length} -> ${filtered.length} результатов`);
  }
  
  // Фильтрация по продукту
  if (product) {
    filtered = filtered.filter(row => Array.isArray(row.metadata.product) ? row.metadata.product.includes(product) : row.metadata.product === product);
  }
  
  // Берём ближайший результат с учётом порога (по модулю)
  console.log(`[RAG] Looking for best result with abs(threshold): ${finalThreshold}`);
  const best = filtered.reduce((acc, row) => {
    if (Math.abs(row.score) <= finalThreshold && (acc === null || Math.abs(row.score) < Math.abs(acc.score))) {
      return row;
    }
    return acc;
  }, null);
  console.log(`[RAG] Best result:`, best);
  
  const result = {
    answer: best?.metadata?.answer,
    context: best?.metadata?.context,
    product: best?.metadata?.product,
    priority: best?.metadata?.priority,
    date: best?.metadata?.date,
    score: best?.score !== undefined && best?.score !== null ? Number(best.score) : null,
  };
  
  console.log(`[RAG] Final result:`, result);
  
  // Кэшируем результат (используем ai-cache вместо ragCache)
  // Используем те же tagIds, что и для проверки кэша
  if (USE_AI_CACHE) {
    const cacheKey = aiCache.generateKeyForRAG(tableId, userQuestion, product, userId, userTagIds);
    aiCache.setWithType(cacheKey, result, 'rag');
    console.log(`[RAG] Результат сохранен в кэш (userId=${userId}, tagIds=${userTagIds ? userTagIds.join(',') : 'null'})`);
  }
  
  return result;
}

/**
 * Получить плейсхолдеры для всех таблиц (генерируются на лету)
 * Плейсхолдеры таблиц НЕ хранятся в БД, генерируются из названий таблиц
 * Возвращает объект: { placeholderName: tableName, ... }
 * Пример: { svyaz_tegov_i_pravil: "Связь тегов и правил", faq: "FAQ" }
 * @returns {Promise<Object>} Объект с плейсхолдерами таблиц
 */
async function getTablePlaceholders() {
  try {
    // Проверяем кэш
    const now = Date.now();
    if (tablePlaceholdersCache.data && (now - tablePlaceholdersCache.timestamp) < TABLE_PLACEHOLDERS_CACHE_TTL) {
      logger.debug('[RAG] Плейсхолдеры таблиц загружены из кэша');
      return tablePlaceholdersCache.data;
    }

    logger.info('[RAG] Генерация плейсхолдеров таблиц...');

    // Получаем все электронные таблицы (user_tables)
    const tables = await encryptedDb.getData('user_tables', {});
    logger.info(`[RAG] Получено таблиц: ${tables.length}`);

    // Генерируем плейсхолдеры из названий таблиц
    const placeholders = {};
    const existingPlaceholders = [];

    for (const table of tables) {
      if (!table.name || typeof table.name !== 'string') {
        continue;
      }

      // Генерируем плейсхолдер из названия таблицы
      let placeholderName = generatePlaceholder(table.name);

      // Проверяем уникальность и добавляем суффикс если нужно
      let candidate = placeholderName;
      let i = 1;
      while (existingPlaceholders.includes(candidate)) {
        candidate = `${placeholderName}_${i}`;
        i++;
        if (i > 1000) {
          candidate = `${placeholderName}_${Date.now()}`;
          break;
        }
      }

      placeholders[candidate] = table.name;
      existingPlaceholders.push(candidate);

      logger.debug(`[RAG] Таблица "${table.name}" → плейсхолдер: {${candidate}}`);
    }

    // Сохраняем в кэш
    tablePlaceholdersCache.data = placeholders;
    tablePlaceholdersCache.timestamp = now;

    logger.info(`[RAG] Сгенерировано плейсхолдеров таблиц: ${Object.keys(placeholders).length}`);
    return placeholders;
  } catch (error) {
    logger.error('[RAG] Ошибка генерации плейсхолдеров таблиц:', error.message);
    return {};
  }
}

/**
 * Инвалидация кэша плейсхолдеров таблиц
 */
function invalidateTablePlaceholdersCache() {
  tablePlaceholdersCache.data = null;
  tablePlaceholdersCache.timestamp = 0;
  logger.info('[RAG] Кэш плейсхолдеров таблиц инвалидирован');
}

/**
 * Загрузка всех плейсхолдеров и их значений из пользовательских таблиц
 * Возвращает объект: { placeholder1: value1, placeholder2: value2, ... }
 * @param {Array} selectedRagTables - Массив ID выбранных RAG таблиц для фильтрации
 */
async function getAllPlaceholdersWithValues(selectedRagTables = []) {
  try {
    console.log('[RAG] Начинаем загрузку плейсхолдеров...');
    
    // Получаем колонки с плейсхолдерами
    let columns = await encryptedDb.getData('user_columns', {});
    
    // Фильтруем по выбранным RAG таблицам, если они указаны
    if (selectedRagTables && selectedRagTables.length > 0) {
      columns = columns.filter(col => selectedRagTables.includes(col.table_id));
      console.log(`[RAG] Фильтруем по RAG таблицам: ${selectedRagTables.join(', ')}`);
    }
    console.log(`[RAG] Получено колонок: ${columns.length}`);
    
    const columnsWithPlaceholders = columns.filter(col => col.placeholder && col.placeholder.trim() !== '');
    console.log(`[RAG] Колонок с плейсхолдерами: ${columnsWithPlaceholders.length}`);
    
    if (columnsWithPlaceholders.length === 0) {
      console.log('[RAG] Нет колонок с плейсхолдерами');
      return {};
    }
    
    // Получаем значения для каждой колонки с плейсхолдером
    const map = {};
    for (const column of columnsWithPlaceholders) {
      try {
        console.log(`[RAG] Получаем значение для плейсхолдера: ${column.placeholder} (column_id: ${column.id})`);
        
        // Получаем первое значение для этой колонки
        const values = await encryptedDb.getData('user_cell_values', { column_id: column.id }, 1);
        console.log(`[RAG] Найдено значений для ${column.placeholder}: ${values ? values.length : 0}`);
        
        if (values && values.length > 0 && values[0].value) {
          map[column.placeholder] = values[0].value;
          console.log(`[RAG] Установлено значение для ${column.placeholder}: ${values[0].value.substring(0, 50)}...`);
        } else {
          console.log(`[RAG] Нет значений для плейсхолдера ${column.placeholder}`);
        }
      } catch (error) {
        console.error(`[RAG] Ошибка получения значения для плейсхолдера ${column.placeholder}:`, error);
      }
    }
    
    console.log(`[RAG] Итоговый объект плейсхолдеров:`, Object.keys(map));
    return map;
  } catch (error) {
    console.error('[RAG] Ошибка получения плейсхолдеров:', error);
    return {};
  }
}

/**
 * Подставляет значения плейсхолдеров в строку (например, systemPrompt)
 * Пример: "Добро пожаловать, {client_name}!" => "Добро пожаловать, ООО Ромашка!"
 */
function replacePlaceholders(str, placeholders) {
  if (!str || typeof str !== 'string') return str;
  return str.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, key) => {
    return key in placeholders ? placeholders[key] : match;
  });
}

function parseIfArray(val) {
  if (typeof val === 'string') {
    try {
      const arr = JSON.parse(val);
      if (Array.isArray(arr)) return arr;
    } catch {}
  }
  return Array.isArray(val) ? val : (val ? [val] : []);
}

/**
 * Выполнить function call (tool call) от ИИ
 * Только функции для обновления имени и тегов пользователя
 * @param {Object} toolCall - Вызов функции от ИИ
 * @param {number} userId - ID пользователя (для функций обновления профиля)
 * @returns {Promise<Object>} Результат выполнения функции
 */
async function executeToolCall(toolCall, userId) {
  const { name, arguments: args } = toolCall.function;
  
  try {
    logger.info(`[RAG] Выполнение function call: ${name}`, args);
    
    if (!userId) {
      return { error: 'userId required for function calling' };
    }
    
    switch (name) {
      case 'update_user_name':
        // Обновление имени пользователя
        const resultName = await profileAnalysisService.updateUserNameInternal(userId, args.name);
        return { 
          success: true, 
          message: `Имя пользователя обновлено: ${args.name}`,
          name: args.name
        };
        
      case 'update_user_tags':
        // Обновление тегов пользователя
        // args.tagNames - массив названий тегов
        const tagIds = await profileAnalysisService.getTagIdsByNames(args.tagNames || []);
        const resultTags = await profileAnalysisService.updateUserTagsInternal(userId, tagIds);
        return { 
          success: true, 
          message: `Теги пользователя обновлены: ${args.tagNames.join(', ')}`,
          tagNames: args.tagNames,
          tagIds: tagIds
        };
        
      default:
        logger.warn(`[RAG] Unknown function call: ${name}`);
        return { error: `Unknown function: ${name}. Available functions: update_user_name, update_user_tags` };
    }
  } catch (error) {
    logger.error(`[RAG] Ошибка выполнения function call ${name}:`, error.message);
    return { error: error.message };
  }
}

/**
 * Получить определения функций для Function Calling
 * Только функции для обновления имени и тегов пользователя
 * @param {number} userId - ID пользователя (для функций обновления профиля)
 * @returns {Array} Массив определений функций
 */
function getFunctionDefinitions(userId) {
  return [
    {
      type: "function",
      function: {
        name: "update_user_name",
        description: "Обновить имя пользователя в профиле. Используй когда пользователь называет свое имя в сообщении. Пример: пользователь говорит 'Меня зовут Иван Петров' → вызывай update_user_name с name='Иван Петров'.",
        parameters: {
          type: "object",
          properties: {
            name: {
              type: "string",
              description: "Полное имя пользователя (например, 'Иван Петров' или 'Мария Иванова')"
            }
          },
          required: ["name"]
        }
      }
    },
    {
      type: "function",
      function: {
        name: "update_user_tags",
        description: "Обновить теги пользователя. Используй когда нужно добавить или изменить теги пользователя на основе контекста беседы. Пример: пользователь говорит 'Я купил ваш продукт' → можно добавить тег 'клиент' или 'холдер'. Пример: пользователь спрашивает про VIP программу → можно добавить тег 'VIP'.",
        parameters: {
          type: "object",
          properties: {
            tagNames: {
              type: "array",
              items: { type: "string" },
              description: "Массив названий тегов для добавления/обновления (например, ['VIP', 'клиент'] или ['холдер'])"
            }
          },
          required: ["tagNames"]
        }
      }
    }
  ];
}

async function generateLLMResponse({
  userQuestion,
  context,
  clarifyingAnswer,
  objectionAnswer,
  answer,
  systemPrompt,
  userTags,
  product,
  priority,
  date,
  rules,
  history,
  model,
  selectedRagTables,
  userId = null, // Добавляем userId для function calling
  multiSourceResults = null, // Результаты мульти-источникового поиска
  userProfile = null
}) {
  console.log(`[RAG] generateLLMResponse called with:`, {
    userQuestion,
    context,
    answer,
    systemPrompt: systemPrompt ? systemPrompt.substring(0, 100) + '...' : 'null',
    userTags,
    product,
    priority,
    date,
    model,
    historyLength: history ? history.length : 0,
    userProfile: userProfile ? {
      name: userProfile.name,
      tags: userProfile.tags,
      nameMissing: userProfile.nameMissing
    } : null
  });

  try {
    const aiAssistant = require('./ai-assistant');
    
    // Создаем контекст беседы с RAG данными
    const conversationContext = createConversationContext({
      userQuestion,
      ragAnswer: answer,
      ragContext: context,
      history,
      product,
      priority,
      date
    }, 'generateLLMResponse');
    
    const conversationSummary = buildConversationSummary(history, {
      maxMessages: 12,
      maxChars: 700,
      snippetLength: 160
    });

    const summaryPrefix = conversationSummary
      ? `Краткая сводка предыдущего диалога:\n${conversationSummary}\n\n`
      : '';

    // Формируем улучшенный промпт для LLM с учетом найденной информации
    let prompt = '';
    
    // Если есть результаты мульти-источникового поиска, используем их
    if (multiSourceResults && multiSourceResults.results && multiSourceResults.results.length > 0) {
      const sourcesInfo = multiSourceResults.results
        .slice(0, 3) // Берем топ-3 результатов
        .map((r, idx) => {
          const sourceName = r.sourceType === 'table' ? `Таблица (ID: ${r.sourceId})` : `Документ: ${r.metadata?.title || r.context || 'Без названия'}`;
          const fallbackText = (r.metadata?.answer && String(r.metadata.answer).trim())
            || (r.metadata?.title && String(r.metadata.title).trim())
            || '(текст отсутствует)';
          const sourceText = (r.text && r.text.trim()) || fallbackText;
          const snippetLimit = 300;
          const truncatedText = sourceText.length > snippetLimit
            ? `${sourceText.slice(0, snippetLimit)}...`
            : sourceText;
          const contextPart = r.context ? `\nКонтекст: ${r.context}` : '';
          return `[Источник ${idx + 1}: ${sourceName}]\n${truncatedText}${contextPart}`;
        })
        .join('\n\n---\n\n');
      
      prompt = `${summaryPrefix}База знаний содержит следующую информацию из разных источников:\n\n${sourcesInfo}\n\nВопрос пользователя: ${userQuestion}\n\nПроанализируй информацию из всех источников и дай пользователю полный и точный ответ.`;
    } else if (answer) {
      // Формат: делаем RAG ответ главным, вопрос - контекстом
      prompt = `${summaryPrefix}База знаний содержит ответ:\n"${answer}"\n\nВопрос пользователя: ${userQuestion}\n\nДай пользователю этот ответ из базы знаний.`;
    }
    
    if (!prompt) {
      prompt = `${summaryPrefix}Вопрос пользователя: ${userQuestion}`;
    }
    
    if (context && !multiSourceResults) {
      prompt += `\n\nДополнительный контекст: ${context}`;
    }
    
    if (product) {
      prompt += `\n\nПродукт: ${product}`;
    }

    if (priority) {
      prompt += `\n\nПриоритет: ${priority}`;
    }

    if (date) {
      prompt += `\n\nДата: ${date}`;
    }

    if (userTags && Array.isArray(userTags) && userTags.length > 0) {
      prompt += `\n\nТеги пользователя: ${userTags.join(', ')}`;
    }

    // --- ДОБАВЛЕНО: подстановка плейсхолдеров ---
    let finalSystemPrompt = systemPrompt;
    if (systemPrompt && systemPrompt.includes('{')) {
      // Подставляем плейсхолдеры таблиц (переменные для ИИ)
      const tablePlaceholders = await getTablePlaceholders();
      finalSystemPrompt = replacePlaceholders(finalSystemPrompt, tablePlaceholders);
      
      // Подставляем плейсхолдеры столбцов (значения из первой строки)
      const columnPlaceholders = await getAllPlaceholdersWithValues(selectedRagTables);
      finalSystemPrompt = replacePlaceholders(finalSystemPrompt, columnPlaceholders);
      
      console.log(`[RAG] Подставлены плейсхолдеры таблиц и столбцов в системный промпт`);
    }
    // --- КОНЕЦ ДОБАВЛЕНИЯ ---

    if (userProfile) {
      const profileLines = [];
      if (userProfile.name) {
        profileLines.push(`Имя пользователя: ${userProfile.name}`);
      } else if (userProfile.nameMissing) {
        profileLines.push('Имя пользователя неизвестно. Вежливо спросите, как к нему обращаться, и дождитесь ответа (например: "Подскажите, пожалуйста, как я могу к вам обращаться?").');
      }

      if (Array.isArray(userProfile.tags) && userProfile.tags.length > 0) {
        profileLines.push(`Активные теги пользователя: ${userProfile.tags.join(', ')}`);
      }

      if (profileLines.length > 0) {
        const profileBlock = `Информация о пользователе:\n${profileLines.join('\n')}`;
        finalSystemPrompt = finalSystemPrompt
          ? `${finalSystemPrompt}\n\n${profileBlock}`
          : profileBlock;
      }
    }

    // Системный промпт полностью настраивается пользователем в /settings/ai/assistant
    // RAG ответ уже добавлен в prompt выше

    console.log(`[RAG] Сформированный промпт:`, prompt.substring(0, 200) + '...');

    // Получаем ответ от AI с учетом истории беседы
    let llmResponse;
    
    // Формируем сообщения для LLM
    const messages = [];
    if (finalSystemPrompt) {
      messages.push({ role: 'system', content: finalSystemPrompt });
    }
    const historyForLLM = Array.isArray(history) ? history.slice(-4) : [];
    for (const h of historyForLLM) {
      if (h && h.content) {
        const role = h.role === 'assistant' ? 'assistant' : 'user';
        messages.push({ role, content: h.content });
      }
    }
    messages.push({ role: 'user', content: prompt });

    // Загружаем параметры LLM и qwen из настроек
    const llmParameters = await aiConfigService.getLLMParameters();
    const qwenParameters = await aiConfigService.getQwenSpecificParameters();
    const ollamaConfig_data = await ollamaConfig.getConfigAsync();
    
    // Формируем тело запроса для Ollama API (используем утилиту)
    const requestBodyOptions = {
      messages: messages,
      model: model,
      llmParameters: llmParameters,
      qwenParameters: qwenParameters,
      defaultModel: ollamaConfig_data.defaultModel,
      stream: false
    };
    
    // Добавляем tools для function calling (если userId передан)
    if (userId) {
      const tools = getFunctionDefinitions(userId);
      requestBodyOptions.tools = tools;
      requestBodyOptions.tool_choice = "auto";
    }
    
    const requestBody = buildOllamaRequest(requestBodyOptions);

    // Получаем настройки Ollama заранее (нужны для всех путей выполнения)
    const ollamaUrl = ollamaConfig.getBaseUrl();
    const timeouts = ollamaConfig.getTimeouts();

    try {
      // ✨ НОВОЕ: Используем очередь (если включена)
      // ВАЖНО: Function calling не поддерживается в очереди, поэтому если tools нужны - используем прямой вызов
      if (USE_AI_QUEUE && !userId) {
        try {
          llmResponse = await aiQueue.addTask({
            messages,
            model: requestBody.model,
            // Передаем параметры для очереди
            llmParameters,
            qwenParameters
          });
          
          console.log('[RAG] LLM response from queue:', llmResponse ? llmResponse.substring(0, 100) + '...' : 'null');
          return llmResponse;
          
        } catch (queueError) {
          console.warn('[RAG] Queue error, fallback to direct call:', queueError.message);
          
          // Fallback: если очередь переполнена и есть ответ из RAG - возвращаем его
          if (queueError.message.includes('переполнена') && answer) {
            console.log('[RAG] Возврат прямого ответа из RAG (очередь переполнена)');
            return answer;
          }
          
          // Продолжаем к прямому вызову
        }
      }

      // Прямой вызов Ollama (если очередь отключена или ошибка очереди)
      
      // Логируем размер промпта для отладки
      const promptSize = JSON.stringify(messages).length;
      const systemPromptSize = messages.find(m => m.role === 'system')?.content?.length || 0;
      const userPromptSize = messages.find(m => m.role === 'user')?.content?.length || 0;
      const historySize = messages.filter(m => m.role !== 'system' && m.role !== 'user').reduce((sum, m) => sum + (m.content?.length || 0), 0);
      
      logger.info(`[RAG] Отправка запроса в Ollama. Размер промпта: ${promptSize} символов (система: ${systemPromptSize}, пользователь: ${userPromptSize}, история: ${historySize}), таймаут: ${timeouts.ollamaChat/1000}с`);
      logger.info(`[RAG] Параметры LLM:`, JSON.stringify(llmParameters));
      if (qwenParameters.format) {
        logger.info(`[RAG] Qwen параметр format: ${qwenParameters.format}`);
      }
      
      // Проверяем размер промпта и предупреждаем, если он большой
      if (promptSize > 10000) {
        logger.warn(`[RAG] ⚠️ Большой промпт (${promptSize} символов). Возможны проблемы с производительностью.`);
      }
      if (promptSize > 50000) {
        logger.error(`[RAG] ⚠️⚠️ ОЧЕНЬ БОЛЬШОЙ промпт (${promptSize} символов). Модель может не справиться.`);
      }
      
      // Логируем информацию о function calling (если включен)
      if (requestBody.tools) {
        logger.info(`[RAG] Function calling включен, доступно ${requestBody.tools.length} функций`);
      }
      
      logger.info(`[RAG] Отправка запроса в Ollama (${ollamaUrl}/api/chat) в ${new Date().toISOString()}...`);
      const requestStartTime = Date.now();
      
      // Добавляем промежуточное логирование для длительных запросов
      const progressInterval = setInterval(() => {
        const elapsed = Date.now() - requestStartTime;
        const elapsedSeconds = Math.round(elapsed/1000);
        if (elapsed > 30000) { // 30 секунд
          logger.warn(`[RAG] Запрос к Ollama выполняется уже ${elapsedSeconds}с...`, {
            model: requestBody.model,
            promptSize,
            timeout: timeouts.ollamaChat / 1000,
            elapsedSeconds,
            remainingTimeout: Math.round((timeouts.ollamaChat - elapsed) / 1000)
          });
        }
        // Критическое предупреждение если осталось менее 30 секунд до таймаута
        if (elapsed > timeouts.ollamaChat - 30000) {
          logger.error(`[RAG] ⚠️⚠️ КРИТИЧНО: Запрос к Ollama выполняется ${elapsedSeconds}с, до таймаута осталось ~${Math.round((timeouts.ollamaChat - elapsed) / 1000)}с!`, {
            model: requestBody.model,
            promptSize,
            timeout: timeouts.ollamaChat / 1000
          });
        }
      }, 15000); // Проверяем каждые 15 секунд (чаще для лучшего мониторинга)
      
      let response;
      try {
        response = await axios.post(`${ollamaUrl}/api/chat`, requestBody, {
          timeout: timeouts.ollamaChat
        });
      } finally {
        clearInterval(progressInterval);
      }
      
      const requestDuration = Date.now() - requestStartTime;
      const durationSeconds = Math.round(requestDuration/1000);
      logger.info(`[RAG] Получен ответ от Ollama в ${new Date().toISOString()}, статус: ${response.status}, время выполнения: ${requestDuration}ms (${durationSeconds}с)`, {
        model: requestBody.model,
        promptSize,
        timeout: timeouts.ollamaChat / 1000,
        responseLength: response.data?.message?.content?.length || 0
      });
      
      // Предупреждение если запрос занял слишком много времени
      if (requestDuration > 60000) { // Больше минуты
        logger.warn(`[RAG] ⚠️ Запрос к Ollama занял ${durationSeconds}с - это слишком долго. Возможные причины: большой промпт (${promptSize} символов), перегруженная модель или медленная система.`);
      }
      
      // ✨ НОВОЕ: Обработка function calls
      if (response.data.message.tool_calls && response.data.message.tool_calls.length > 0) {
        logger.info(`[RAG] ИИ запросил выполнение ${response.data.message.tool_calls.length} функций`);
        
        const toolResults = [];
        
        // Выполняем все function calls
        for (const toolCall of response.data.message.tool_calls) {
          const result = await executeToolCall(toolCall, userId);
          toolResults.push({
            tool_call_id: toolCall.id,
            role: 'tool',
            name: toolCall.function.name,
            content: JSON.stringify(result)
          });
        }
        
        // Добавляем результаты в историю сообщений
        messages.push(response.data.message); // Сообщение с tool_calls
        messages.push(...toolResults); // Результаты выполнения функций
        
        // Повторяем запрос с результатами функций
        const finalRequestBody = {
          ...requestBody,
          messages: messages
        };
        
        // Убираем tools из финального запроса (они уже не нужны)
        delete finalRequestBody.tools;
        delete finalRequestBody.tool_choice;
        
        logger.info(`[RAG] Отправка финального запроса в Ollama после выполнения function calls...`);
        const finalRequestStartTime = Date.now();
        const finalPromptSize = JSON.stringify(finalRequestBody.messages).length;
        
        // Мониторинг второго запроса
        const finalProgressInterval = setInterval(() => {
          const elapsed = Date.now() - finalRequestStartTime;
          const elapsedSeconds = Math.round(elapsed/1000);
          if (elapsed > 30000) {
            logger.warn(`[RAG] Финальный запрос к Ollama (после function calls) выполняется уже ${elapsedSeconds}с...`, {
              model: finalRequestBody.model,
              promptSize: finalPromptSize,
              timeout: timeouts.ollamaChat / 1000,
              elapsedSeconds
            });
          }
        }, 15000);
        
        let finalResponse;
        try {
          finalResponse = await axios.post(`${ollamaUrl}/api/chat`, finalRequestBody, {
            timeout: timeouts.ollamaChat
          });
        } finally {
          clearInterval(finalProgressInterval);
        }
        
        const finalRequestDuration = Date.now() - finalRequestStartTime;
        const finalDurationSeconds = Math.round(finalRequestDuration/1000);
        
        llmResponse = finalResponse.data.message.content;
        logger.info(`[RAG] Получен финальный ответ после выполнения function calls, длина: ${llmResponse ? llmResponse.length : 0} символов, время выполнения: ${finalRequestDuration}ms (${finalDurationSeconds}с)`, {
          model: finalRequestBody.model,
          promptSize: finalPromptSize,
          responseLength: llmResponse?.length || 0
        });
        
        if (finalRequestDuration > 60000) {
          logger.warn(`[RAG] ⚠️ Финальный запрос к Ollama (после function calls) занял ${finalDurationSeconds}с - это слишком долго.`);
        }
      } else {
        llmResponse = response.data.message.content;
        logger.info(`[RAG] Получен ответ от Ollama, длина: ${llmResponse ? llmResponse.length : 0} символов`);
      }
      
    } catch (error) {
      const isTimeout = error.message && (
        error.message.includes('timeout') || 
        error.message.includes('ETIMEDOUT') ||
        error.message.includes('ECONNABORTED')
      );
      
      logger.error(`[RAG] Ошибка при вызове Ollama:`, {
        message: error.message,
        code: error.code,
        isTimeout,
        stack: error.stack
      });
      
      if (isTimeout) {
        logger.warn(`[RAG] Ollama timeout после ${timeouts.ollamaChat/1000}с. Возможно, модель перегружена или контекст слишком большой.`);
      } else {
        logger.error(`[RAG] Error in Ollama call:`, error.message, error.stack);
      }
      
      // Финальный fallback - возврат ответа из RAG
      if (answer) {
        logger.info('[RAG] Возврат прямого ответа из RAG (ошибка Ollama)');
        return answer;
      }
      
      // Если был таймаут и нет ответа из RAG - возвращаем более информативное сообщение
      if (isTimeout) {
        return 'Извините, обработка запроса заняла слишком много времени. Пожалуйста, попробуйте упростить ваш вопрос или повторите попытку позже.';
      }
      
      return 'Извините, произошла ошибка при генерации ответа.';
    }

    console.log(`[RAG] LLM response generated:`, llmResponse ? (typeof llmResponse === 'string' ? llmResponse.substring(0, 100) + '...' : JSON.stringify(llmResponse).substring(0, 100) + '...') : 'null');
    return llmResponse;
  } catch (error) {
    console.error(`[RAG] Error generating LLM response:`, error);
    return 'Извините, произошла ошибка при генерации ответа.';
  }
}


function buildConversationSummary(history, options = {}) {
  const {
    maxMessages = 10,
    maxChars = 700,
    snippetLength = 160
  } = options;

  if (!Array.isArray(history) || history.length === 0) {
    return null;
  }

  const recentMessages = history.slice(-Math.max(maxMessages, 1));
  const roleLabels = {
    assistant: 'Ассистент',
    system: 'Система',
    tool: 'Инструмент'
  };

  const lines = [];
  let totalLength = 0;

  for (const message of recentMessages) {
    if (!message || typeof message.content !== 'string') {
      continue;
    }

    const roleLabel = roleLabels[message.role] || 'Пользователь';
    let text = message.content.replace(/\s+/g, ' ').trim();
    if (!text) {
      continue;
    }

    if (text.length > snippetLength) {
      text = `${text.slice(0, snippetLength)}...`;
    }

    const line = `${roleLabel}: ${text}`;
    if (totalLength + line.length > maxChars) {
      break;
    }

    lines.push(line);
    totalLength += line.length + 1;
    if (totalLength >= maxChars) {
      break;
    }
  }

  return lines.length > 0 ? lines.join('\n') : null;
}


/**
 * Создает контекст беседы с RAG данными
 */
function createConversationContext({
  userQuestion,
  ragAnswer,
  ragContext,
  history,
  product,
  priority,
  date
}, source = 'generic') {
  const context = {
    currentQuestion: userQuestion,
    ragData: {
      answer: ragAnswer,
      context: ragContext,
      product,
      priority,
      date
    },
    conversationHistory: history || [],
    hasRagData: !!(ragAnswer || ragContext),
    isFollowUpQuestion: history && history.length > 0
  };

  console.log(`[RAG] Создан контекст беседы (${source}):`, {
    hasRagData: context.hasRagData,
    historyLength: context.conversationHistory.length,
    isFollowUp: context.isFollowUpQuestion
  });

  return context;
}

/**
 * Улучшенная функция RAG с поддержкой беседы
 */
async function ragAnswerWithConversation({ 
  tableId, 
  userQuestion, 
  product = null, 
  threshold = null,
  history = [],
  conversationId = null,
  forceReindex = false
}) {
  // Загружаем настройки RAG для threshold
  const ragConfig = await aiConfigService.getRAGConfig();
  const finalThreshold = threshold !== null ? threshold : ragConfig.threshold;
  
  console.log(`[RAG] ragAnswerWithConversation: tableId=${tableId}, question="${userQuestion}", historyLength=${history.length}, userId=${userId}`);

  // Получаем базовый RAG результат (с фильтрацией по тегам, если userId передан)
  const ragResult = await ragAnswer({ tableId, userQuestion, product, threshold: finalThreshold, forceReindex, userId });
  
  // Анализируем контекст беседы
  const conversationContext = createConversationContext({
    userQuestion,
    ragAnswer: ragResult.answer,
    ragContext: ragResult.context,
    history,
    product: ragResult.product,
    priority: ragResult.priority,
    date: ragResult.date
  }, 'ragAnswerWithConversation');

  // Если это уточняющий вопрос и есть история
  if (conversationContext.isFollowUpQuestion && conversationContext.hasRagData) {
    console.log(`[RAG] Обнаружен уточняющий вопрос с RAG данными`);

    // Проверяем, есть ли точный ответ в первом поиске
    if (ragResult.answer && typeof ragResult.score === 'number' && Math.abs(ragResult.score) <= finalThreshold) {
      console.log(`[RAG] Найден точный ответ (score=${ragResult.score}), возвращаем ответ из базы без модификаций`);
      return {
        ...ragResult,
        // Возвращаем чистый ответ
        answer: ragResult.answer,
        conversationContext,
        isFollowUp: true
      };
    }
    
    // Модифицируем вопрос с учетом контекста (only if no confident match)
    const contextualQuestion = `${userQuestion}\n\nКонтекст предыдущих ответов: ${history.map(msg => msg.content).join('\n')}`;
    
    // Повторяем поиск с контекстуализированным вопросом
    const contextualRagResult = await ragAnswer({ 
      tableId, 
      userQuestion: contextualQuestion, 
      product, 
      threshold: finalThreshold,
      forceReindex,
      userId
    });
    
    // Объединяем результаты
    return {
      ...contextualRagResult,
      conversationContext,
      isFollowUp: true
    };
  }

  return {
    ...ragResult,
    conversationContext,
    isFollowUp: false
  };
}

// ✨ НОВОЕ: Функция для запуска AI Queue Worker
function startQueueWorker() {
  if (USE_AI_QUEUE) {
    aiQueue.startWorker();
    logger.info('[RAG] ✅ AI Queue Worker запущен из ragService');
  } else {
    logger.info('[RAG] AI Queue отключена (USE_AI_QUEUE=false)');
  }
}

// ✨ НОВОЕ: Функция для остановки AI Queue Worker
function stopQueueWorker() {
  if (aiQueue && aiQueue.workerInterval) {
    aiQueue.stopWorker();
    logger.info('[RAG] ⏹️ AI Queue Worker остановлен');
  }
}

// ✨ НОВОЕ: Получение статистики
function getQueueStats() {
  return aiQueue.getStats();
}

function getCacheStats() {
  return {
    ...aiCache.getStats(),
    byType: aiCache.getStatsByType()
  };
}

module.exports = {
  ragAnswer,
  getTableData,
  generateLLMResponse,
  ragAnswerWithConversation,
  startQueueWorker,
  stopQueueWorker,
  getQueueStats,
  getCacheStats,
  getAllPlaceholdersWithValues, // Плейсхолдеры столбцов (значения из первой строки)
  getTablePlaceholders, // Плейсхолдеры таблиц (генерируются на лету)
  invalidateTablePlaceholdersCache, // Инвалидация кэша плейсхолдеров таблиц
  replacePlaceholders, // Функция подстановки плейсхолдеров
  generatePlaceholder // Функция генерации плейсхолдера из названия
};

