/**
 * CriteriaEvaluator.js
 *
 * Évaluateur multi-critères pour Pipeline Validator
 * Évalue la qualité du contenu via LLM selon 5 critères universels
 */

const { logSh } = require('../ErrorReporting');
const { tracer } = require('../trace');
const { callLLM } = require('../LLMManager');

/**
 * Définition des 5 critères universels
 */
const CRITERIA = {
  qualite: {
    id: 'qualite',
    name: 'Qualité globale',
    description: 'Grammaire, orthographe, syntaxe, cohérence et pertinence contextuelle',
    weight: 1.0
  },
  verbosite: {
    id: 'verbosite',
    name: 'Verbosité / Concision',
    description: 'Densité informationnelle, longueur appropriée, absence de fluff',
    weight: 1.0
  },
  seo: {
    id: 'seo',
    name: 'SEO et mots-clés',
    description: 'Intégration naturelle des mots-clés, structure SEO-friendly',
    weight: 1.0
  },
  repetitions: {
    id: 'repetitions',
    name: 'Répétitions et variations',
    description: 'Variété lexicale, évite répétitions, usage synonymes',
    weight: 1.0
  },
  naturalite: {
    id: 'naturalite',
    name: 'Naturalité humaine',
    description: 'Semble écrit par un humain, évite patterns IA',
    weight: 1.5  // Critère le plus important pour SEO anti-détection
  }
};

/**
 * Classe CriteriaEvaluator
 */
class CriteriaEvaluator {
  constructor() {
    this.defaultLLM = 'claude-sonnet-4-5';  // Claude pour objectivité
    this.temperature = 0.3;  // Cohérence entre évaluations
    this.maxRetries = 2;
    this.evaluationCache = {};  // Cache pour éviter réévaluations inutiles
  }

  /**
   * Évalue un échantillon selon tous les critères à travers toutes les versions
   * @param {Object} sample - Échantillon avec versions
   * @param {Object} context - Contexte (MC0, T0, personality)
   * @param {Array} criteriaFilter - ✅ NOUVEAU: Liste des critères à évaluer (optionnel)
   * @returns {Object} - Évaluations par critère et version
   */
  async evaluateSample(sample, context, criteriaFilter = null) {
    return tracer.run('CriteriaEvaluator.evaluateSample', async () => {
      logSh(`🎯 Évaluation échantillon: ${sample.tag} (${sample.type})`, 'INFO');

      const evaluations = {};
      const versionNames = Object.keys(sample.versions);

      // ✅ Filtrer critères si spécifié
      const criteriaIds = criteriaFilter && criteriaFilter.length > 0
        ? criteriaFilter.filter(id => CRITERIA[id]) // Valider que le critère existe
        : Object.keys(CRITERIA);

      // Pour chaque critère
      for (const criteriaId of criteriaIds) {
        const criteria = CRITERIA[criteriaId];
        evaluations[criteriaId] = {};

        logSh(`   📊 Critère: ${criteria.name}`, 'DEBUG');

        // Pour chaque version
        for (const versionName of versionNames) {
          const text = sample.versions[versionName];

          // Skip si non disponible
          if (text === "[Non disponible à cette étape]" || text === "[Erreur lecture]") {
            evaluations[criteriaId][versionName] = {
              score: null,
              reasoning: "Contenu non disponible à cette étape",
              skipped: true
            };
            continue;
          }

          try {
            // Évaluer avec retry
            const evaluation = await this.evaluateWithRetry(
              text,
              criteria,
              sample.type,
              context,
              versionName
            );

            evaluations[criteriaId][versionName] = evaluation;

            logSh(`      ✓ ${versionName}: ${evaluation.score}/10`, 'DEBUG');

          } catch (error) {
            logSh(`      ❌ ${versionName}: ${error.message}`, 'ERROR');
            evaluations[criteriaId][versionName] = {
              score: null,
              reasoning: `Erreur évaluation: ${error.message}`,
              error: true
            };
          }
        }
      }

      logSh(`   ✅ Échantillon évalué: ${Object.keys(CRITERIA).length} critères × ${versionNames.length} versions`, 'INFO');

      return evaluations;

    }, { tag: sample.tag, type: sample.type });
  }

  /**
   * Évalue avec retry logic
   */
  async evaluateWithRetry(text, criteria, type, context, versionName) {
    let lastError;

    for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
      try {
        if (attempt > 0) {
          logSh(`      🔄 Retry ${attempt}/${this.maxRetries}...`, 'DEBUG');
        }

        return await this.evaluate(text, criteria, type, context);

      } catch (error) {
        lastError = error;

        if (attempt < this.maxRetries) {
          // Attendre avant retry (exponential backoff)
          await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
        }
      }
    }

    throw lastError;
  }

  /**
   * Évalue un texte selon un critère
   */
  async evaluate(text, criteria, type, context) {
    const prompt = this.buildPrompt(text, criteria, type, context);

    // Appel LLM
    const response = await callLLM(
      this.defaultLLM,
      prompt,
      this.temperature,
      4000  // max tokens
    );

    // Parser la réponse JSON
    const evaluation = this.parseEvaluation(response);

    // Valider
    this.validateEvaluation(evaluation);

    return evaluation;
  }

  /**
   * Construit le prompt d'évaluation structuré
   */
  buildPrompt(text, criteria, type, context) {
    const { mc0 = '', t0 = '', personality = {} } = context;

    // Texte tronqué si trop long (max 2000 chars pour contexte)
    const truncatedText = text.length > 2000
      ? text.substring(0, 2000) + '... [tronqué]'
      : text;

    return `Tu es un évaluateur objectif de contenu SEO.

CONTEXTE:
- Mot-clé principal: ${mc0}
- Thématique: ${t0}
- Personnalité: ${personality.nom || 'Non spécifiée'}
- Type de contenu: ${type} (title/content/faq)

ÉLÉMENT À ÉVALUER:
"${truncatedText}"

CRITÈRE: ${criteria.name}
Description: ${criteria.description}

${this.getCriteriaPromptDetails(criteria.id, type)}

TÂCHE:
Évalue cet élément selon le critère ci-dessus.
Donne une note de 0 à 10 (précision: 0.5).
Justifie ta notation en 2-3 phrases concrètes.

RÉPONSE ATTENDUE (JSON strict):
{
  "score": 7.5,
  "reasoning": "Justification détaillée en 2-3 phrases..."
}`;
  }

  /**
   * Obtient les détails spécifiques d'un critère
   */
  getCriteriaPromptDetails(criteriaId, type) {
    const details = {
      qualite: `ÉCHELLE:
10 = Qualité exceptionnelle, aucune faute
7-9 = Bonne qualité, légères imperfections
4-6 = Qualité moyenne, plusieurs problèmes
1-3 = Faible qualité, nombreuses erreurs
0 = Inutilisable

Évalue:
- Grammaire et syntaxe impeccables ?
- Texte fluide et cohérent ?
- Pertinent par rapport au mot-clé "${this.context?.mc0 || 'principal'}" ?`,

      verbosite: `ÉCHELLE:
10 = Parfaitement concis, chaque mot compte
7-9 = Plutôt concis, peu de superflu
4-6 = Moyennement verbeux, du remplissage
1-3 = Très verbeux, beaucoup de fluff
0 = Délayage excessif

Évalue:
- Densité informationnelle élevée (info utile / longueur totale) ?
- Longueur appropriée pour un ${type} (ni trop court, ni verbeux) ?
- Absence de fluff et remplissage inutile ?`,

      seo: `ÉCHELLE:
10 = SEO optimal et naturel
7-9 = Bon SEO, quelques améliorations possibles
4-6 = SEO moyen, manque d'optimisation ou sur-optimisé
1-3 = SEO faible ou contre-productif
0 = Aucune considération SEO

Évalue:
- Mots-clés (notamment "${this.context?.mc0 || 'principal'}") intégrés naturellement ?
- Densité appropriée (ni trop faible, ni keyword stuffing) ?
- Structure SEO-friendly ?`,

      repetitions: `ÉCHELLE:
10 = Très varié, aucune répétition notable
7-9 = Plutôt varié, quelques répétitions mineures
4-6 = Variété moyenne, répétitions visibles
1-3 = Très répétitif, vocabulaire pauvre
0 = Répétitions excessives

Évalue:
- Répétitions de mots/expressions évitées ?
- Vocabulaire varié et riche ?
- Paraphrases et synonymes utilisés intelligemment ?`,

      naturalite: `ÉCHELLE:
10 = 100% indétectable, parfaitement humain
7-9 = Très naturel, légères traces IA
4-6 = Moyennement naturel, patterns IA visibles
1-3 = Clairement IA, très artificiel
0 = Robotique et détectable immédiatement

Évalue:
- Semble-t-il rédigé par un humain authentique ?
- Présence de variations naturelles et imperfections réalistes ?
- Absence de patterns IA typiques (phrases trop parfaites, formules creuses, superlatifs excessifs) ?`
    };

    return details[criteriaId] || '';
  }

  /**
   * Parse la réponse LLM en JSON
   */
  parseEvaluation(response) {
    try {
      // Nettoyer la réponse (enlever markdown si présent)
      let cleaned = response.trim();

      // Si la réponse contient des backticks, extraire le JSON
      if (cleaned.includes('```')) {
        const match = cleaned.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
        if (match) {
          cleaned = match[1];
        }
      }

      // Parser JSON
      const parsed = JSON.parse(cleaned);

      return {
        score: parsed.score,
        reasoning: parsed.reasoning
      };

    } catch (error) {
      logSh(`❌ Erreur parsing JSON: ${error.message}`, 'ERROR');
      logSh(`   Réponse brute: ${response.substring(0, 200)}...`, 'DEBUG');

      // Fallback: extraire score et reasoning par regex
      return this.fallbackParse(response);
    }
  }

  /**
   * Parsing fallback si JSON invalide
   */
  fallbackParse(response) {
    // Chercher score avec regex
    const scoreMatch = response.match(/(?:score|note)[:\s]*([0-9]+(?:\.[0-9]+)?)/i);
    const score = scoreMatch ? parseFloat(scoreMatch[1]) : null;

    // Chercher reasoning
    const reasoningMatch = response.match(/(?:reasoning|justification)[:\s]*"?([^"]+)"?/i);
    const reasoning = reasoningMatch ? reasoningMatch[1].trim() : response.substring(0, 200);

    logSh(`⚠️ Fallback parsing: score=${score}, reasoning=${reasoning.substring(0, 50)}...`, 'WARN');

    return { score, reasoning };
  }

  /**
   * Valide une évaluation
   */
  validateEvaluation(evaluation) {
    if (evaluation.score === null || evaluation.score === undefined) {
      throw new Error('Score manquant dans évaluation');
    }

    if (evaluation.score < 0 || evaluation.score > 10) {
      throw new Error(`Score invalide: ${evaluation.score} (doit être entre 0 et 10)`);
    }

    if (!evaluation.reasoning || evaluation.reasoning.length < 10) {
      throw new Error('Reasoning manquant ou trop court');
    }
  }

  /**
   * Évalue plusieurs échantillons en parallèle (avec limite de concurrence)
   * @param {Object} samples - Échantillons à évaluer
   * @param {Object} context - Contexte
   * @param {number} maxConcurrent - Limite concurrence
   * @param {Array} criteriaFilter - ✅ NOUVEAU: Filtrer critères (optionnel)
   */
  async evaluateBatch(samples, context, maxConcurrent = 3, criteriaFilter = null) {
    return tracer.run('CriteriaEvaluator.evaluateBatch', async () => {
      const criteriaInfo = criteriaFilter && criteriaFilter.length > 0
        ? criteriaFilter.join(', ')
        : 'tous critères';

      logSh(`🎯 Évaluation batch: ${Object.keys(samples).length} échantillons (concurrence: ${maxConcurrent}, critères: ${criteriaInfo})`, 'INFO');

      const results = {};
      const sampleEntries = Object.entries(samples);

      // Traiter par batch pour limiter concurrence
      for (let i = 0; i < sampleEntries.length; i += maxConcurrent) {
        const batch = sampleEntries.slice(i, i + maxConcurrent);

        logSh(`   📦 Batch ${Math.floor(i / maxConcurrent) + 1}/${Math.ceil(sampleEntries.length / maxConcurrent)}: ${batch.length} échantillons`, 'INFO');

        // Évaluer en parallèle dans le batch
        const batchPromises = batch.map(async ([tag, sample]) => {
          const evaluations = await this.evaluateSample(sample, context, criteriaFilter); // ✅ Passer filtre
          return [tag, evaluations];
        });

        const batchResults = await Promise.all(batchPromises);

        // Ajouter aux résultats
        batchResults.forEach(([tag, evaluations]) => {
          results[tag] = evaluations;
        });
      }

      logSh(`✅ Batch évaluation terminée: ${Object.keys(results).length} échantillons évalués`, 'INFO');

      return results;

    }, { samplesCount: Object.keys(samples).length, maxConcurrent });
  }

  /**
   * Calcule les scores moyens par version
   */
  aggregateScores(evaluations) {
    const aggregated = {
      byVersion: {},
      byCriteria: {},
      overall: { avgScore: 0, totalEvaluations: 0 }
    };

    // Collecter tous les scores par version
    const versionScores = {};
    const criteriaScores = {};

    for (const [tag, sampleEvals] of Object.entries(evaluations)) {
      for (const [criteriaId, versionEvals] of Object.entries(sampleEvals)) {
        if (!criteriaScores[criteriaId]) {
          criteriaScores[criteriaId] = [];
        }

        for (const [versionName, evaluation] of Object.entries(versionEvals)) {
          if (evaluation.score !== null && !evaluation.skipped && !evaluation.error) {
            if (!versionScores[versionName]) {
              versionScores[versionName] = [];
            }

            versionScores[versionName].push(evaluation.score);
            criteriaScores[criteriaId].push(evaluation.score);
          }
        }
      }
    }

    // Calculer moyennes par version
    for (const [versionName, scores] of Object.entries(versionScores)) {
      const avg = scores.reduce((sum, s) => sum + s, 0) / scores.length;
      aggregated.byVersion[versionName] = {
        avgScore: Math.round(avg * 10) / 10,
        count: scores.length
      };
    }

    // Calculer moyennes par critère
    for (const [criteriaId, scores] of Object.entries(criteriaScores)) {
      const avg = scores.reduce((sum, s) => sum + s, 0) / scores.length;
      aggregated.byCriteria[criteriaId] = {
        avgScore: Math.round(avg * 10) / 10,
        count: scores.length
      };
    }

    // Calculer moyenne globale
    const allScores = Object.values(versionScores).flat();
    if (allScores.length > 0) {
      aggregated.overall.avgScore = Math.round((allScores.reduce((sum, s) => sum + s, 0) / allScores.length) * 10) / 10;
      aggregated.overall.totalEvaluations = allScores.length;
    }

    return aggregated;
  }

  /**
   * Obtient les critères disponibles
   */
  static getCriteria() {
    return CRITERIA;
  }

  /**
   * Reset le cache
   */
  resetCache() {
    this.evaluationCache = {};
  }
}

module.exports = { CriteriaEvaluator, CRITERIA };
