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

/**
 * Profile Analysis Service
 * Анализирует сообщения пользователя и автоматически обновляет профиль
 */

const axios = require('axios');
const logger = require('../utils/logger');
const ollamaConfig = require('./ollamaConfig');
const aiConfigService = require('./aiConfigService');
const userContextService = require('./userContextService');
const encryptedDb = require('./encryptedDatabaseService');
const db = require('../db');

/**
 * Извлечь имя пользователя из сообщения через LLM (JSON mode)
 * @param {string} message - Сообщение пользователя
 * @returns {Promise<Object>} { name: string|null, should_update_name: boolean }
 */
async function extractName(message) {
  try {
    logger.info(`[ProfileAnalysis] Начало извлечения имени из сообщения: "${message.substring(0, 50)}${message.length > 50 ? '...' : ''}"`);

    const ollamaUrl = await ollamaConfig.getBaseUrlAsync();
    const llmModel = await ollamaConfig.getDefaultModelAsync();
    const llmParameters = await aiConfigService.getLLMParameters();
    const timeouts = await aiConfigService.getTimeouts();

    const nameExtractionTimeout = Math.min(timeouts.ollamaChat || 60000, 60000);
    logger.info(`[ProfileAnalysis] Параметры для извлечения имени: model=${llmModel}, timeout=${nameExtractionTimeout/1000}с`);

    const prompt = `Определи, указал ли пользователь своё имя в сообщении.

Сообщение: "${message}"

Требования:
- Пользователь пишет по-русски, возможны формулировки вроде "я Алекс", "меня зовут Анна", "это Иван".
- Если явно видишь имя, нормализуй его (первая буква прописная, без лишних слов) и установи "should_update_name": true.
- Если имя не указано или есть сомнения, верни name=null и should_update_name=false.
- Если уверенность ниже 0.7, не обновляй имя.
- Верни строго JSON-объект одной строкой: {"name":string|null,"should_update_name":boolean,"confidence":number}.`;

    const extractStartTime = Date.now();
    logger.info(`[ProfileAnalysis] Отправка запроса к Ollama для извлечения имени...`);
    
    const response = await axios.post(`${ollamaUrl}/api/chat`, {
      model: llmModel,
      messages: [
        {
          role: 'user',
          content: prompt
        }
      ],
      temperature: llmParameters.temperature,
      num_predict: llmParameters.maxTokens,
      top_p: llmParameters.top_p,
      top_k: llmParameters.top_k,
      repeat_penalty: llmParameters.repeat_penalty,
      format: 'json', // Используем JSON mode для structured output
      stream: false
    }, {
      timeout: nameExtractionTimeout
    });

    const extractDuration = Date.now() - extractStartTime;
    logger.info(`[ProfileAnalysis] Получен ответ от Ollama для извлечения имени за ${extractDuration}ms, статус: ${response.status}`);
    
    // Приводим ответ к объекту (учитываем возможную строку при потоковом ответе)
    let payload = response.data;
    if (!payload) {
      logger.error('[ProfileAnalysis] Ответ от Ollama не содержит data:', response);
      return { name: null, should_update_name: false, confidence: 0 };
    }

    if (typeof payload === 'string') {
      try {
        const lines = payload.trim().split('\n').filter(Boolean);
        const lastLine = lines.length > 0 ? lines[lines.length - 1] : payload;
        payload = JSON.parse(lastLine);
      } catch (parseError) {
        logger.error('[ProfileAnalysis] Не удалось распарсить строковый ответ Ollama:', {
          error: parseError.message,
          preview: payload.substring(0, 200)
        });
        return { name: null, should_update_name: false, confidence: 0 };
      }
    }

    if (Array.isArray(payload)) {
      payload = payload[payload.length - 1] || {};
    }
    
    const messagePayload = payload.message || payload.response || null;
    if (!messagePayload || !messagePayload.content) {
      logger.error('[ProfileAnalysis] Ответ от Ollama не содержит message.content:', payload);
      return { name: null, should_update_name: false, confidence: 0 };
    }
    
    let result;
    try {
      result = JSON.parse(messagePayload.content);
    } catch (parseError) {
      logger.error('[ProfileAnalysis] Ошибка парсинга JSON из ответа Ollama:', {
        content: messagePayload.content,
        error: parseError.message
      });
      return { name: null, should_update_name: false, confidence: 0 };
    }
    
    logger.info(`[ProfileAnalysis] Результат извлечения имени: name=${result.name || 'null'}, should_update=${result.should_update_name}, confidence=${result.confidence || 'N/A'}`);

    let normalizedName = typeof result.name === 'string' ? result.name.trim() : '';
    if (normalizedName) {
      normalizedName = normalizedName.replace(/\s+/g, ' ');
      normalizedName = normalizedName.replace(/^["'«»]+|["'«»]+$/g, '');
      if (normalizedName.length === 1) {
        normalizedName = normalizedName.toUpperCase();
      } else {
        normalizedName = normalizedName.charAt(0).toUpperCase() + normalizedName.slice(1);
      }
    }

    const rawConfidence = Number(result.confidence);
    const confidence = Number.isFinite(rawConfidence)
      ? Math.max(0, Math.min(1, rawConfidence))
      : (normalizedName ? 1 : 0);

    if (confidence < 0.7 || !normalizedName) {
      if (normalizedName && confidence < 0.7) {
        logger.debug(`[ProfileAnalysis] Низкая уверенность в имени (${confidence}), не обновляем`);
      }
      return { name: null, should_update_name: false, confidence };
    }

    const shouldUpdate = typeof result.should_update_name === 'boolean'
      ? result.should_update_name
      : !!normalizedName;

    const finalResult = {
      name: normalizedName || null,
      should_update_name: shouldUpdate,
      confidence
    };
    
    logger.debug(`[ProfileAnalysis] Финальный результат извлечения имени:`, finalResult);
    return finalResult;
  } catch (error) {
    logger.error('[ProfileAnalysis] Ошибка извлечения имени:', {
      message: error.message,
      stack: error.stack,
      response: error.response?.data ? JSON.stringify(error.response.data).substring(0, 200) : undefined
    });
    return { name: null, should_update_name: false, confidence: 0 };
  }
}

/**
 * Найти таблицу по названию
 * @param {string} tableName - Название таблицы
 * @returns {Promise<Object|null>} Таблица или null
 */
async function findTableByName(tableName) {
  try {
    const tables = await encryptedDb.getData('user_tables', {});
    return tables.find(table => table.name === tableName) || null;
  } catch (error) {
    logger.error(`[ProfileAnalysis] Ошибка поиска таблицы "${tableName}":`, error.message);
    return null;
  }
}

/**
 * Получить столбец по названию
 * @param {number} tableId - ID таблицы
 * @param {string} columnName - Название столбца
 * @returns {Promise<Object|null>} Столбец или null
 */
async function getColumnByName(tableId, columnName) {
  try {
    const columns = await encryptedDb.getData('user_columns', { table_id: tableId });
    return columns.find(col => col.name === columnName) || null;
  } catch (error) {
    logger.error(`[ProfileAnalysis] Ошибка поиска столбца "${columnName}":`, error.message);
    return null;
  }
}

/**
 * Получить значение ячейки
 * @param {number} rowId - ID строки
 * @param {number} columnId - ID столбца
 * @returns {Promise<string|null>} Значение ячейки или null
 */
async function getCellValue(rowId, columnId) {
  try {
    const cellValues = await encryptedDb.getData('user_cell_values', { 
      row_id: rowId,
      column_id: columnId
    }, 1);
    return cellValues && cellValues.length > 0 ? cellValues[0].value : null;
  } catch (error) {
    logger.error(`[ProfileAnalysis] Ошибка получения значения ячейки:`, error.message);
    return null;
  }
}

/**
 * Получить строки таблицы
 * @param {number} tableId - ID таблицы
 * @returns {Promise<Array>} Массив строк
 */
async function getTableRows(tableId) {
  try {
    return await encryptedDb.getData('user_rows', { table_id: tableId });
  } catch (error) {
    logger.error(`[ProfileAnalysis] Ошибка получения строк таблицы:`, error.message);
    return [];
  }
}

/**
 * Парсить теги из ячейки (multiselect-relation или строка)
 * @param {*} tagsCell - Значение ячейки с тегами
 * @returns {Array<string>} Массив названий тегов
 */
function parseTagsFromCell(tagsCell) {
  if (!tagsCell) return [];
  
  // Если это массив
  if (Array.isArray(tagsCell)) {
    return tagsCell.map(String);
  }
  
  // Если это строка JSON
  if (typeof tagsCell === 'string') {
    try {
      const parsed = JSON.parse(tagsCell);
      if (Array.isArray(parsed)) {
        return parsed.map(String);
      }
    } catch (e) {
      // Не JSON, возможно просто строка с названиями через запятую
      return tagsCell.split(',').map(t => t.trim()).filter(Boolean);
    }
  }
  
  return [];
}

/**
 * Найти ключевые слова в сообщении
 * @param {string} message - Сообщение пользователя
 * @param {number} keywordsTableId - ID таблицы "Ключевые слова и теги"
 * @returns {Promise<Array>} Массив найденных строк с ключевыми словами
 */
async function findKeywordsInMessage(message, keywordsTableId) {
  try {
    const rows = await getTableRows(keywordsTableId);
    
    // Получаем столбец "Ключевое слово"
    const keywordColumn = await getColumnByName(keywordsTableId, 'Ключевое слово');
    if (!keywordColumn) {
      logger.warn('[ProfileAnalysis] Столбец "Ключевое слово" не найден');
      return [];
    }

    const messageLower = message.toLowerCase();
    const foundKeywords = [];

    for (const row of rows) {
      const keywordsCell = await getCellValue(row.id, keywordColumn.id);
      if (!keywordsCell) continue;

      // Парсим ключевые слова (разделены запятой)
      const keywords = keywordsCell.split(',').map(k => k.trim().toLowerCase());

      // Проверяем, есть ли хотя бы одно ключевое слово в сообщении
      if (keywords.some(kw => messageLower.includes(kw))) {
        foundKeywords.push(row);
      }
    }

    return foundKeywords;
  } catch (error) {
    logger.error('[ProfileAnalysis] Ошибка поиска ключевых слов:', error.message);
    return [];
  }
}

/**
 * Получить теги по ключевым словам из таблицы
 * @param {Array} foundKeywordRows - Массив строк с найденными ключевыми словами
 * @param {number} keywordsTableId - ID таблицы "Ключевые слова и теги"
 * @returns {Promise<Array>} Массив объектов { tagNames: Array, action: string }
 */
async function getTagsByKeywords(foundKeywordRows, keywordsTableId) {
  try {
    const tagsColumn = await getColumnByName(keywordsTableId, 'Теги');
    const actionColumn = await getColumnByName(keywordsTableId, 'Действие');

    const results = [];

    for (const row of foundKeywordRows) {
      const tagsCell = tagsColumn ? await getCellValue(row.id, tagsColumn.id) : null;
      const action = actionColumn ? await getCellValue(row.id, actionColumn.id) : null;

      const tagNames = parseTagsFromCell(tagsCell);

      if (tagNames.length > 0) {
        results.push({
          tagNames,
          action: action || 'добавить'
        });
      }
    }

    return results;
  } catch (error) {
    logger.error('[ProfileAnalysis] Ошибка получения тегов по ключевым словам:', error.message);
    return [];
  }
}

/**
 * Найти tagIds по названиям тегов
 * @param {Array<string>} tagNames - Массив названий тегов
 * @returns {Promise<Array<number>>} Массив tagIds
 */
async function getTagIdsByNames(tagNames) {
  if (!tagNames || tagNames.length === 0) {
    return [];
  }

  try {
    // Находим таблицу "Теги клиентов"
    const tagsTable = await findTableByName('Теги клиентов');
    if (!tagsTable) {
      logger.warn('[ProfileAnalysis] Таблица "Теги клиентов" не найдена');
      return [];
    }

    const allColumns = await encryptedDb.getData('user_columns', { table_id: tagsTable.id });

    // Подбираем колонку с названием тега по приоритету
    const nameColumn =
      allColumns.find(col => col.options && col.options.purpose === 'userTags') ||
      allColumns.find(col => col.name === 'Название') ||
      allColumns.find(col => col.name === 'Список тегов') ||
      allColumns.find(col => col.type === 'text');

    if (!nameColumn) {
      logger.warn('[ProfileAnalysis] Столбец с названием тега не найден');
      return [];
    }

    const rows = await getTableRows(tagsTable.id);

    const tagIds = [];
    for (const row of rows) {
      const cellValue = await getCellValue(row.id, nameColumn.id);
      if (!cellValue) {
        continue;
      }

      const normalizedNames = parseTagsFromCell(cellValue);
      if (normalizedNames.some(name => tagNames.includes(name))) {
        tagIds.push(row.id);
      }
    }

    return tagIds;
  } catch (error) {
    logger.error('[ProfileAnalysis] Ошибка поиска tagIds по названиям:', error.message);
    return [];
  }
}

/**
 * Обработать действие (заменить/добавить теги)
 * @param {string} action - Действие (например, "заменить клиент" или "добавить")
 * @param {Array<string>} currentTagNames - Текущие названия тегов
 * @param {Array<string>} newTagNames - Новые названия тегов
 * @returns {Array<string>} Результирующие названия тегов
 */
function processAction(action, currentTagNames, newTagNames) {
  if (!action || action === 'добавить') {
    // Добавляем новые теги к существующим
    return [...new Set([...currentTagNames, ...newTagNames])];
  }

  // Обработка действия "заменить <тег>"
  if (action.startsWith('заменить')) {
    const tagToReplace = action.replace('заменить', '').trim();
    
    // Удаляем тег для замены
    const filtered = currentTagNames.filter(tag => tag !== tagToReplace);
    
    // Добавляем новые теги
    return [...new Set([...filtered, ...newTagNames])];
  }

  // По умолчанию - добавляем
  return [...new Set([...currentTagNames, ...newTagNames])];
}

/**
 * Анализировать сообщение пользователя и обновить профиль
 * @param {number} userId - ID пользователя
 * @param {string} message - Сообщение пользователя
 * @returns {Promise<Object>} { name: string|null, suggestedTags: Array<string> }
 */
async function analyzeUserMessage(userId, message) {
  try {
    logger.info(`[ProfileAnalysis] Анализ сообщения пользователя ${userId}`);
    logger.info(`[ProfileAnalysis] Сообщение пользователя ${userId}: "${message.substring(0, 100)}${message.length > 100 ? '...' : ''}"`);

    const DEFAULT_TAG_NAME = 'Без лицензии';
    const isGuest = userContextService.isGuestId(userId);

    let currentContext = null;
    let currentName = null;
    let currentTagIds = [];
    let currentTagNames = [];
    let suggestedTags = [];
    let nameMissing = false;

    if (!isGuest) {
      currentContext = await userContextService.getUserContext(userId);
      currentName = currentContext?.name || null;
      currentTagIds = Array.isArray(currentContext?.tags) ? [...currentContext.tags] : await userContextService.getUserTags(userId);
      currentTagNames = Array.isArray(currentContext?.tagNames) && currentContext.tagNames.length > 0
        ? [...currentContext.tagNames]
        : await userContextService.getTagNames(currentTagIds);

      if (!currentTagIds || currentTagIds.length === 0) {
        const defaultTagIds = await getTagIdsByNames([DEFAULT_TAG_NAME]);
        if (defaultTagIds.length > 0) {
          await updateUserTagsInternal(userId, defaultTagIds);
          userContextService.invalidateUserCache(userId);
          currentTagIds = [...defaultTagIds];
          currentTagNames = [DEFAULT_TAG_NAME];
          logger.info(`[ProfileAnalysis] Пользователю ${userId} автоматически назначен тег по умолчанию: ${DEFAULT_TAG_NAME}`);
        } else {
          logger.warn(`[ProfileAnalysis] Тег по умолчанию "${DEFAULT_TAG_NAME}" не найден в базе`);
        }
      }

      suggestedTags = [...currentTagNames];
      nameMissing = !currentName;
    }

    // 1. Извлечение имени из сообщения
    logger.info(`[ProfileAnalysis] Начало извлечения имени для пользователя ${userId}...`);
    const nameResult = await extractName(message);
    logger.info(`[ProfileAnalysis] Извлечение имени завершено: name=${nameResult.name || 'null'}, should_update=${nameResult.should_update_name}`);
    
    // 2. Обновление имени пользователя (если нужно)
    if (!isGuest && nameResult.name) {
      const shouldUpdateName = nameResult.should_update_name || !currentName;
      if (shouldUpdateName && (currentName || '') !== nameResult.name) {
        logger.info(`[ProfileAnalysis] Обновление имени пользователя ${userId}: "${currentName || ''}" → "${nameResult.name}"`);
        await updateUserNameInternal(userId, nameResult.name);
        userContextService.invalidateUserCache(userId);
        currentName = nameResult.name;
        nameMissing = false;
      }
    }

    // 3. Поиск ключевых слов в таблице "Ключевые слова и теги"
    const keywordsTable = await findTableByName('Ключевые слова и теги');

    if (!isGuest && keywordsTable) {
      const foundKeywords = await findKeywordsInMessage(message, keywordsTable.id);
      
      if (foundKeywords.length > 0) {
        // Получаем теги по ключевым словам
        const tagsByKeywords = await getTagsByKeywords(foundKeywords, keywordsTable.id);
        
        // Обрабатываем действия и собираем новые теги
        let allTagNames = [...currentTagNames];
        
        for (const { tagNames, action } of tagsByKeywords) {
          allTagNames = processAction(action, allTagNames, tagNames);
        }

        // Находим tagIds по названиям
        const tagIds = await getTagIdsByNames(allTagNames);
        
        // Обновляем теги пользователя (если есть изменения)
        const hasChanges = tagIds.length !== currentTagIds.length || !tagIds.every(id => currentTagIds.includes(id));
        if (hasChanges) {
          logger.info(`[ProfileAnalysis] Обновление тегов пользователя ${userId}: ${currentTagNames.join(', ') || '—'} → ${allTagNames.join(', ')}`);
          await updateUserTagsInternal(userId, tagIds);
          userContextService.invalidateUserCache(userId);
          currentTagIds = [...tagIds];
          currentTagNames = [...allTagNames];
        }

        suggestedTags = [...currentTagNames];
      }
    }

    return {
      name: nameResult.name,
      should_update_name: !isGuest && nameResult.name ? (nameResult.should_update_name || !currentName) : false,
      suggestedTags,
      currentName,
      currentTagNames,
      nameMissing: !isGuest ? nameMissing : false
    };
  } catch (error) {
    logger.error('[ProfileAnalysis] Ошибка анализа сообщения:', error.message);
    return { name: null, should_update_name: false, suggestedTags: [], currentName: null, currentTagNames: [], nameMissing: false };
  }
}

/**
 * Внутренняя функция для обновления имени пользователя (без проверки прав)
 * @param {number} userId - ID пользователя
 * @param {string} name - Новое имя
 */
async function updateUserNameInternal(userId, name) {
  try {
    const encryptionUtils = require('../utils/encryptionUtils');
    const encryptionKey = encryptionUtils.getEncryptionKey();
    
    const nameParts = name.trim().split(' ');
    const firstName = nameParts[0] || '';
    const lastName = nameParts.slice(1).join(' ') || '';

    await db.getQuery()(
      `UPDATE users 
       SET first_name_encrypted = encrypt_text($1, $3), 
           last_name_encrypted = encrypt_text($2, $3)
       WHERE id = $4`,
      [firstName, lastName, encryptionKey, userId]
    );

    // Инвалидируем кэш пользователя
    userContextService.invalidateUserCache(userId);
    
    // Отправляем WebSocket уведомление об обновлении контакта
    const { broadcastContactsUpdate } = require('../wsHub');
    broadcastContactsUpdate();
    
    logger.info(`[ProfileAnalysis] Имя пользователя ${userId} обновлено: "${name}"`);
  } catch (error) {
    logger.error(`[ProfileAnalysis] Ошибка обновления имени пользователя ${userId}:`, error.message);
    throw error;
  }
}

/**
 * Внутренняя функция для обновления тегов пользователя (без проверки прав)
 * @param {number} userId - ID пользователя
 * @param {Array<number>} tagIds - Массив tagIds
 */
async function updateUserTagsInternal(userId, tagIds) {
  try {
    // Удаляем старые связи
    await db.getQuery()('DELETE FROM user_tag_links WHERE user_id = $1', [userId]);
    
    // Добавляем новые связи
    for (const tagId of tagIds) {
      await db.getQuery()(
        'INSERT INTO user_tag_links (user_id, tag_id) VALUES ($1, $2) ON CONFLICT DO NOTHING',
        [userId, tagId]
      );
    }

    // Отправляем WebSocket уведомление
    const { broadcastTagsUpdate } = require('../wsHub');
    broadcastTagsUpdate(null, userId);

    // Инвалидируем кэш пользователя
    userContextService.invalidateUserCache(userId);
    
    logger.info(`[ProfileAnalysis] Теги пользователя ${userId} обновлены: ${tagIds.join(', ')}`);
  } catch (error) {
    logger.error(`[ProfileAnalysis] Ошибка обновления тегов пользователя ${userId}:`, error.message);
    throw error;
  }
}

module.exports = {
  analyzeUserMessage,
  extractName,
  findKeywordsInMessage,
  getTagsByKeywords,
  getTagIdsByNames,
  processAction,
  updateUserNameInternal,
  updateUserTagsInternal,
  findTableByName,
  getColumnByName,
  getCellValue,
  getTableRows
};

