// ========================================
// FICHIER: LLMManager.js
// Description: Hub central pour tous les appels LLM (Version Node.js)
// Support: Claude, OpenAI, Gemini, Deepseek, Moonshot, Mistral
// ========================================

const fetch = globalThis.fetch.bind(globalThis);
const { logSh } = require('./ErrorReporting');

// Charger les variables d'environnement
require('dotenv').config();

// ============= CONFIGURATION CENTRALISÉE =============
// IDs basés sur les MODÈLES (pas les providers) pour garantir la reproductibilité

const LLM_CONFIG = {
  // OpenAI Models - GPT-5 Series (August 2025)
  'gpt-5': {
    provider: 'openai',
    apiKey: process.env.OPENAI_API_KEY,
    endpoint: 'https://api.openai.com/v1/chat/completions',
    model: 'gpt-5',
    displayName: 'GPT-5',
    headers: {
      'Authorization': 'Bearer {API_KEY}',
      'Content-Type': 'application/json'
    },
    temperature: 0.7,
    maxTokens: 16000,  // GPT-5 utilise reasoning tokens (reasoning_effort=minimal forcé)
    timeout: 300000,
    retries: 3
  },

  'gpt-5-mini': {
    provider: 'openai',
    apiKey: process.env.OPENAI_API_KEY,
    endpoint: 'https://api.openai.com/v1/chat/completions',
    model: 'gpt-5-mini',
    displayName: 'GPT-5 Mini',
    headers: {
      'Authorization': 'Bearer {API_KEY}',
      'Content-Type': 'application/json'
    },
    temperature: 0.7,
    maxTokens: 8000,  // GPT-5-mini utilise reasoning tokens (reasoning_effort=minimal forcé)
    timeout: 300000,
    retries: 3
  },

  'gpt-5-nano': {
    provider: 'openai',
    apiKey: process.env.OPENAI_API_KEY,
    endpoint: 'https://api.openai.com/v1/chat/completions',
    model: 'gpt-5-nano',
    displayName: 'GPT-5 Nano',
    headers: {
      'Authorization': 'Bearer {API_KEY}',
      'Content-Type': 'application/json'
    },
    temperature: 0.7,
    maxTokens: 4000,  // GPT-5-nano utilise reasoning tokens (reasoning_effort=minimal forcé)
    timeout: 300000,
    retries: 3
  },

  // OpenAI Models - GPT-4o Series
  'gpt-4o-mini': {
    provider: 'openai',
    apiKey: process.env.OPENAI_API_KEY,
    endpoint: 'https://api.openai.com/v1/chat/completions',
    model: 'gpt-4o-mini',
    displayName: 'GPT-4o Mini',
    headers: {
      'Authorization': 'Bearer {API_KEY}',
      'Content-Type': 'application/json'
    },
    temperature: 0.7,
    maxTokens: 6000,  // Augmenté pour contraintes de longueur
    timeout: 300000,
    retries: 3
  },

  // Claude Models
  'claude-sonnet-4-5': {
    provider: 'anthropic',
    apiKey: process.env.ANTHROPIC_API_KEY,
    endpoint: 'https://api.anthropic.com/v1/messages',
    model: 'claude-sonnet-4-5-20250929',
    displayName: 'Claude Sonnet 4.5',
    headers: {
      'x-api-key': '{API_KEY}',
      'Content-Type': 'application/json',
      'anthropic-version': '2023-06-01'
    },
    temperature: 0.7,
    maxTokens: 6000,
    timeout: 300000,
    retries: 6
  },

  // Google Models
  'gemini-pro': {
    provider: 'google',
    apiKey: process.env.GOOGLE_API_KEY,
    endpoint: 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent',
    model: 'gemini-pro',
    displayName: 'Google Gemini Pro',
    headers: {
      'Content-Type': 'application/json'
    },
    temperature: 0.7,
    maxTokens: 6000,  // Augmenté pour contraintes de longueur
    timeout: 300000,
    retries: 3
  },

  // Deepseek Models
  'deepseek-chat': {
    provider: 'deepseek',
    apiKey: process.env.DEEPSEEK_API_KEY,
    endpoint: 'https://api.deepseek.com/v1/chat/completions',
    model: 'deepseek-chat',
    displayName: 'Deepseek Chat',
    headers: {
      'Authorization': 'Bearer {API_KEY}',
      'Content-Type': 'application/json'
    },
    temperature: 0.7,
    maxTokens: 6000,  // Augmenté pour contraintes de longueur
    timeout: 300000,
    retries: 3
  },

  // Moonshot Models
  'moonshot-v1-32k': {
    provider: 'moonshot',
    apiKey: process.env.MOONSHOT_API_KEY,
    endpoint: 'https://api.moonshot.ai/v1/chat/completions',
    model: 'moonshot-v1-32k',
    displayName: 'Moonshot v1 32K',
    headers: {
      'Authorization': 'Bearer {API_KEY}',
      'Content-Type': 'application/json'
    },
    temperature: 0.7,
    maxTokens: 6000,  // Augmenté pour contraintes de longueur
    timeout: 300000,
    retries: 3
  },

  // Mistral Models
  'mistral-small': {
    provider: 'mistral',
    apiKey: process.env.MISTRAL_API_KEY,
    endpoint: 'https://api.mistral.ai/v1/chat/completions',
    model: 'mistral-small-latest',
    displayName: 'Mistral Small',
    headers: {
      'Authorization': 'Bearer {API_KEY}',
      'Content-Type': 'application/json'
    },
    temperature: 0.7,
    maxTokens: 5000,
    timeout: 300000,
    retries: 3
  }
};

// ============= HELPER FUNCTIONS =============

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// ============= INTERFACE UNIVERSELLE =============

/**
 * Fonction principale pour appeler n'importe quel LLM
 * @param {string} llmProvider - claude|openai|deepseek|moonshot|mistral
 * @param {string} prompt - Le prompt à envoyer
 * @param {object} options - Options personnalisées (température, tokens, etc.)
 * @param {object} personality - Personnalité pour contexte système
 * @returns {Promise<string>} - Réponse générée
 */
async function callLLM(llmProvider, prompt, options = {}, personality = null) {
  const startTime = Date.now();
  
  try {
    // Vérifier si le provider existe
    if (!LLM_CONFIG[llmProvider]) {
      throw new Error(`Provider LLM inconnu: ${llmProvider}`);
    }
    
    // Vérifier si l'API key est configurée
    const config = LLM_CONFIG[llmProvider];
    if (!config.apiKey || config.apiKey.startsWith('VOTRE_CLE_')) {
      throw new Error(`Clé API manquante pour ${llmProvider}`);
    }
    
    // 📤 LOG PROMPT (une seule fois)
    logSh(`\n📤 ===== PROMPT ENVOYÉ À ${llmProvider.toUpperCase()} (${config.model}) | PERSONNALITÉ: ${personality?.nom || 'AUCUNE'} =====`, 'PROMPT');
    logSh(prompt, 'PROMPT');

    // Préparer la requête selon le provider
    const requestData = buildRequestData(llmProvider, prompt, options, personality);

    // Effectuer l'appel avec retry logic
    const response = await callWithRetry(llmProvider, requestData, config);

    // Parser la réponse selon le format du provider
    const content = parseResponse(llmProvider, response);

    // 📥 LOG RESPONSE
    logSh(`\n📥 ===== RÉPONSE REÇUE DE ${llmProvider.toUpperCase()} (${config.model}) | Durée: ${Date.now() - startTime}ms =====`, 'LLM');
    logSh(content, 'LLM');

    const duration = Date.now() - startTime;
    logSh(`✅ ${llmProvider.toUpperCase()} (${personality?.nom || 'sans personnalité'}) réponse en ${duration}ms`, 'INFO');
    
    // Enregistrer les stats d'usage
    await recordUsageStats(llmProvider, prompt.length, content.length, duration);
    
    return content;
    
  } catch (error) {
    const duration = Date.now() - startTime;
    logSh(`❌ Erreur ${llmProvider.toUpperCase()} (${personality?.nom || 'sans personnalité'}): ${error.toString()}`, 'ERROR');
    
    // Enregistrer l'échec
    await recordUsageStats(llmProvider, prompt.length, 0, duration, error.toString());
    
    throw error;
  }
}

// ============= CONSTRUCTION DES REQUÊTES =============

function buildRequestData(modelId, prompt, options, personality) {
  const config = LLM_CONFIG[modelId];
  let temperature = options.temperature || config.temperature;
  let maxTokens = options.maxTokens || config.maxTokens;

  // Anthropic Claude: temperature must be 0-1 (clamp if needed)
  if (config.provider === 'anthropic' && temperature > 1.0) {
    logSh(`   ⚠️ Claude: temperature clamped from ${temperature.toFixed(2)} to 1.0 (API limit)`, 'WARNING');
    temperature = 1.0;
  }

  // GPT-5: Force minimum tokens (reasoning tokens + content tokens)
  if (modelId.startsWith('gpt-5')) {
    const MIN_GPT5_TOKENS = 1500; // Minimum pour reasoning + contenu
    if (maxTokens < MIN_GPT5_TOKENS) {
      logSh(`   ⚠️ GPT-5: maxTokens augmenté de ${maxTokens} à ${MIN_GPT5_TOKENS} (minimum pour reasoning)`, 'WARNING');
      maxTokens = MIN_GPT5_TOKENS;
    }
  }

  // Construire le système prompt si personnalité fournie
  const systemPrompt = personality ?
    `Tu es ${personality.nom}. ${personality.description}. Style: ${personality.style}` :
    'Tu es un assistant expert.';

  // Switch sur le PROVIDER (pas le modelId)
  switch (config.provider) {
    case 'openai':
    case 'deepseek':
    case 'moonshot':
    case 'mistral':
      // GPT-5 models use max_completion_tokens instead of max_tokens
      const tokenField = modelId.startsWith('gpt-5') ? 'max_completion_tokens' : 'max_tokens';

      // GPT-5 models only support temperature: 1 (default)
      const requestBody = {
        model: config.model,
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: prompt }
        ],
        [tokenField]: maxTokens,
        stream: false
      };

      // Only add temperature if NOT GPT-5 (GPT-5 only supports default temperature=1)
      if (!modelId.startsWith('gpt-5')) {
        requestBody.temperature = temperature;
      }

      // GPT-5: Force minimal reasoning effort to reduce reasoning tokens
      if (modelId.startsWith('gpt-5')) {
        requestBody.reasoning_effort = 'minimal';
        logSh(`   🧠 GPT-5: reasoning_effort=minimal, max_completion_tokens=${maxTokens}`, 'DEBUG');
      }

      return requestBody;

    case 'anthropic':
      return {
        model: config.model,
        max_tokens: maxTokens,
        temperature: temperature,
        system: systemPrompt,
        messages: [
          { role: 'user', content: prompt }
        ]
      };

    case 'google':
      // Format spécifique Gemini
      return {
        contents: [{
          parts: [{ text: systemPrompt + '\n\n' + prompt }]
        }],
        generationConfig: {
          temperature: temperature,
          maxOutputTokens: maxTokens
        }
      };

    default:
      throw new Error(`Format de requête non supporté pour provider ${config.provider}`);
  }
}

// ============= APPELS AVEC RETRY =============

async function callWithRetry(provider, requestData, config) {
  let lastError;
  
  for (let attempt = 1; attempt <= config.retries; attempt++) {
    try {
      logSh(`🔄 Tentative ${attempt}/${config.retries} pour ${provider.toUpperCase()}`, 'DEBUG');
      
      // Préparer les headers avec la clé API
      const headers = {};
      Object.keys(config.headers).forEach(key => {
        headers[key] = config.headers[key].replace('{API_KEY}', config.apiKey);
      });
      
      // URL standard
      let url = config.endpoint;
      
      const options = {
        method: 'POST',
        headers: headers,
        body: JSON.stringify(requestData),
        timeout: config.timeout
      };
      
      const response = await fetch(url, options);
      const responseText = await response.text();
      
      if (response.ok) {
        return JSON.parse(responseText);
      } else if (response.status === 429) {
        // Rate limiting - attendre plus longtemps
        const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff
        logSh(`⏳ Rate limit ${provider.toUpperCase()}, attente ${waitTime}ms`, 'WARNING');
        await sleep(waitTime);
        continue;
      } else {
        throw new Error(`HTTP ${response.status}: ${responseText}`);
      }
      
    } catch (error) {
      lastError = error;
      
      if (attempt < config.retries) {
        const waitTime = 1000 * attempt;
        logSh(`⚠ Erreur tentative ${attempt}: ${error.toString()}, retry dans ${waitTime}ms`, 'WARNING');
        await sleep(waitTime);
      }
    }
  }
  
  throw new Error(`Échec après ${config.retries} tentatives: ${lastError.toString()}`);
}

// ============= PARSING DES RÉPONSES =============

function parseResponse(modelId, responseData) {
  const config = LLM_CONFIG[modelId];

  try {
    switch (config.provider) {
      case 'openai':
      case 'deepseek':
      case 'moonshot':
      case 'mistral':
        return responseData.choices[0].message.content.trim();

      case 'anthropic':
        return responseData.content[0].text.trim();

      case 'google':
        return responseData.candidates[0].content.parts[0].text.trim();

      default:
        throw new Error(`Parser non supporté pour provider ${config.provider}`);
    }
  } catch (error) {
    logSh(`❌ Erreur parsing ${modelId} (${config.provider}): ${error.toString()}`, 'ERROR');
    logSh(`Response brute: ${JSON.stringify(responseData)}`, 'DEBUG');
    throw new Error(`Impossible de parser la réponse ${modelId}: ${error.toString()}`);
  }
}

// ============= GESTION DES STATISTIQUES =============

async function recordUsageStats(provider, promptTokens, responseTokens, duration, error = null) {
  try {
    // Vérifier que le provider existe dans la config
    if (!LLM_CONFIG[provider]) {
      logSh(`⚠ Stats: Provider inconnu "${provider}", skip stats`, 'DEBUG');
      return;
    }

    // TODO: Adapter selon votre système de stockage Node.js
    // Peut être une base de données, un fichier, MongoDB, etc.
    const statsData = {
      timestamp: new Date(),
      provider: provider,
      model: LLM_CONFIG[provider].model,
      promptTokens: promptTokens,
      responseTokens: responseTokens,
      duration: duration,
      error: error || ''
    };

    // Exemple: log vers console ou fichier
    logSh(`📊 Stats: ${JSON.stringify(statsData)}`, 'DEBUG');

    // TODO: Implémenter sauvegarde réelle (DB, fichier, etc.)

  } catch (statsError) {
    // Ne pas faire planter le workflow si les stats échouent
    logSh(`⚠ Erreur enregistrement stats: ${statsError.toString()}`, 'WARNING');
  }
}

// ============= FONCTIONS UTILITAIRES =============

/**
 * Tester la connectivité de tous les LLMs
 */
async function testAllLLMs() {
  const testPrompt = "Dis bonjour en 5 mots maximum.";
  const results = {};
  
  const allProviders = Object.keys(LLM_CONFIG);
  
  for (const provider of allProviders) {
    try {
      logSh(`🧪 Test ${provider}...`, 'INFO');
      
      const response = await callLLM(provider, testPrompt);
      results[provider] = {
        status: 'SUCCESS',
        response: response,
        model: LLM_CONFIG[provider].model
      };
      
    } catch (error) {
      results[provider] = {
        status: 'ERROR',
        error: error.toString(),
        model: LLM_CONFIG[provider].model
      };
    }
    
    // Petit délai entre tests
    await sleep(500);
  }
  
  logSh(`📊 Tests terminés: ${JSON.stringify(results, null, 2)}`, 'INFO');
  return results;
}

/**
 * Obtenir les providers disponibles (avec clés API valides)
 */
function getAvailableProviders() {
  const available = [];

  Object.keys(LLM_CONFIG).forEach(provider => {
    const config = LLM_CONFIG[provider];
    if (config.apiKey && !config.apiKey.startsWith('VOTRE_CLE_')) {
      available.push(provider);
    }
  });

  return available;
}

/**
 * Obtenir la liste complète des providers pour UI/validation
 * @returns {Array} Liste des providers avec id, name, model
 */
function getLLMProvidersList() {
  const providers = [];

  Object.entries(LLM_CONFIG).forEach(([id, config]) => {
    providers.push({
      id: id,
      name: config.displayName,
      model: config.model,
      provider: config.provider,
      default: id === 'claude-sonnet-4-5' // Claude Sonnet 4.5 par défaut
    });
  });

  return providers;
}

/**
 * Obtenir des statistiques d'usage par provider
 */
async function getUsageStats() {
  try {
    // TODO: Adapter selon votre système de stockage
    // Pour l'instant retourne un message par défaut
    return { message: 'Statistiques non implémentées en Node.js' };
    
  } catch (error) {
    return { error: error.toString() };
  }
}

// ============= MIGRATION DE L'ANCIEN CODE =============

/**
 * Fonction de compatibilité pour remplacer votre ancien callOpenAI()
 * Maintient la même signature pour ne pas casser votre code existant
 */
async function callOpenAI(prompt, personality) {
  return await callLLM('gpt-4o-mini', prompt, {}, personality);
}

// ============= EXPORTS POUR TESTS =============

/**
 * Fonction de test rapide
 */
async function testLLMManager() {
  logSh('🚀 Test du LLM Manager Node.js...', 'INFO');
  
  // Test des providers disponibles
  const available = getAvailableProviders();
  logSh('Providers disponibles: ' + available.join(', ') + ' (' + available.length + '/5)', 'INFO');
  
  // Test d'appel simple sur chaque provider disponible
  for (const provider of available) {
    try {
      logSh(`🧪 Test ${provider}...`, 'DEBUG');
      const startTime = Date.now();
      
      const response = await callLLM(provider, 'Dis juste "Test OK"');
      const duration = Date.now() - startTime;
      
      logSh(`✅ Test ${provider} réussi: "${response}" (${duration}ms)`, 'INFO');
      
    } catch (error) {
      logSh(`❌ Test ${provider} échoué: ${error.toString()}`, 'ERROR');
    }
    
    // Petit délai pour éviter rate limits
    await sleep(500);
  }
  
  // Test spécifique OpenAI (compatibilité avec ancien code)
  try {
    logSh('🎯 Test spécifique OpenAI (compatibilité)...', 'DEBUG');
    const response = await callLLM('gpt-4o-mini', 'Dis juste "Test OK"');
    logSh('✅ Test OpenAI compatibilité: ' + response, 'INFO');
  } catch (error) {
    logSh('❌ Test OpenAI compatibilité échoué: ' + error.toString(), 'ERROR');
  }
  
  // Afficher les stats d'usage
  try {
    logSh('📊 Récupération statistiques d\'usage...', 'DEBUG');
    const stats = await getUsageStats();
    
    if (stats.error) {
      logSh('⚠ Erreur récupération stats: ' + stats.error, 'WARNING');
    } else if (stats.message) {
      logSh('📊 Stats: ' + stats.message, 'INFO');
    } else {
      // Formatter les stats pour les logs
      Object.keys(stats).forEach(provider => {
        const s = stats[provider];
        logSh(`📈 ${provider}: ${s.calls} appels, ${s.successRate}% succès, ${s.avgDuration}ms moyen`, 'INFO');
      });
    }
  } catch (error) {
    logSh('❌ Erreur lors de la récupération des stats: ' + error.toString(), 'ERROR');
  }
  
  // Résumé final
  const workingCount = available.length;
  const totalProviders = Object.keys(LLM_CONFIG).length;
  
  if (workingCount === totalProviders) {
    logSh(`✅ Test LLM Manager COMPLET: ${workingCount}/${totalProviders} providers opérationnels`, 'INFO');
  } else if (workingCount >= 2) {
    logSh(`✅ Test LLM Manager PARTIEL: ${workingCount}/${totalProviders} providers opérationnels (suffisant pour DNA Mixing)`, 'INFO');
  } else {
    logSh(`❌ Test LLM Manager INSUFFISANT: ${workingCount}/${totalProviders} providers opérationnels (minimum 2 requis)`, 'ERROR');
  }
  
  logSh('🏁 Test LLM Manager terminé', 'INFO');
}

/**
 * Version complète avec test de tous les providers (même non configurés)
 */
async function testLLMManagerComplete() {
  logSh('🚀 Test COMPLET du LLM Manager (tous providers)...', 'INFO');
  
  const allProviders = Object.keys(LLM_CONFIG);
  logSh(`Providers configurés: ${allProviders.join(', ')}`, 'INFO');
  
  const results = {
    configured: 0,
    working: 0,
    failed: 0
  };
  
  for (const provider of allProviders) {
    const config = LLM_CONFIG[provider];
    
    // Vérifier si configuré
    if (!config.apiKey || config.apiKey.startsWith('VOTRE_CLE_')) {
      logSh(`⚙️ ${provider}: NON CONFIGURÉ (clé API manquante)`, 'WARNING');
      continue;
    }
    
    results.configured++;
    
    try {
      logSh(`🧪 Test ${provider} (${config.model})...`, 'DEBUG');
      const startTime = Date.now();
      
      const response = await callLLM(provider, 'Réponds "OK" seulement.', { maxTokens: 100 });
      const duration = Date.now() - startTime;
      
      results.working++;
      logSh(`✅ ${provider}: "${response.trim()}" (${duration}ms)`, 'INFO');
      
    } catch (error) {
      results.failed++;
      logSh(`❌ ${provider}: ${error.toString()}`, 'ERROR');
    }
    
    // Délai entre tests
    await sleep(700);
  }
  
  // Résumé final complet
  logSh(`📊 RÉSUMÉ FINAL:`, 'INFO');
  logSh(`   • Providers total: ${allProviders.length}`, 'INFO');
  logSh(`   • Configurés: ${results.configured}`, 'INFO');
  logSh(`   • Fonctionnels: ${results.working}`, 'INFO');
  logSh(`   • En échec: ${results.failed}`, 'INFO');
  
  const status = results.working >= 4 ? 'EXCELLENT' : 
                 results.working >= 2 ? 'BON' : 'INSUFFISANT';
                 
  logSh(`🏆 STATUS: ${status} (${results.working} LLMs opérationnels)`, 
        status === 'INSUFFISANT' ? 'ERROR' : 'INFO');
  
  logSh('🏁 Test LLM Manager COMPLET terminé', 'INFO');
  
  return {
    total: allProviders.length,
    configured: results.configured,
    working: results.working,
    failed: results.failed,
    status: status
  };
}

// ============= EXPORTS MODULE =============

module.exports = {
  callLLM,
  callOpenAI,
  testAllLLMs,
  getAvailableProviders,
  getLLMProvidersList,
  getUsageStats,
  testLLMManager,
  testLLMManagerComplete,
  LLM_CONFIG
};

