/**
 * Cyber-Assistant - Module de validation des données
 * Valide les données avant sauvegarde dans localStorage
 *
 * Ce module est optionnel et n'affecte pas le fonctionnement si absent.
 * Il ajoute une couche de vérification pour détecter les anomalies.
 */
(function() {
  'use strict';

  // Schémas de validation par type d'entité
  const SCHEMAS = {
    action: {
      required: ['id', 'title'],
      types: {
        id: 'string',
        title: 'string',
        description: 'string',
        progress: 'number',
        priority: 'string',
        status: 'string',
        owner: 'string',
        dueDate: 'string',
        linkType: 'string',
        linkId: 'stringOrArray'
      },
      arrays: ['comments', 'criticalAssetIds'],
      defaults: {
        progress: 0,
        status: 'planifie',
        priority: 'medium'
      }
    },

    risk: {
      required: ['id', 'title'],
      types: {
        id: 'string',
        title: 'string',
        description: 'string',
        impact: 'number',
        probability: 'number',
        score: 'number',
        level: 'string',
        treatment: 'string'
      },
      arrays: ['actionIds', 'comments', 'criticalAssetIds'],
      ranges: {
        impact: { min: 1, max: 5 },
        probability: { min: 1, max: 5 },
        score: { min: 1, max: 25 }
      }
    },

    control: {
      required: ['id', 'title'],
      types: {
        id: 'string',
        title: 'string',
        description: 'string',
        status: 'string',
        cmm: 'string',
        category: 'string',
        reference: 'string',
        owner: 'string',
        reviewer: 'string',
        reviewFrequency: 'string',
        evidence: 'string',
        lastEvaluation: 'string',
        nextReviewDate: 'string'
      },
      arrays: ['subcategory', 'comments', 'recommendations'],
      validValues: {
        status: ['non_implemente', 'en_cours', 'implemente', 'verifie'],
        cmm: ['Initial', 'Répétable', 'Défini', 'Géré', 'Optimisé'],
        category: ['Organisationnel', 'Humain', 'Physique', 'Technologique', 'Non classé']
      }
    },

    threat: {
      required: ['id', 'title'],
      types: {
        id: 'string',
        title: 'string',
        description: 'string',
        impact: 'number',
        likelihood: 'number',
        score: 'number',
        level: 'string',
        status: 'string'
      },
      arrays: ['actionIds', 'criticalAssetIds', 'comments']
    },

    nonconformity: {
      required: ['id', 'title'],
      types: {
        id: 'string',
        title: 'string',
        type: 'string',
        status: 'string',
        detectionDate: 'string',
        owner: 'string',
        auditId: 'string'
      },
      arrays: ['criticalAssetIds', 'comments']
    },

    audit: {
      required: ['id', 'title'],
      types: {
        id: 'string',
        title: 'string',
        type: 'string',
        scope: 'string',
        plannedDate: 'string',
        executionDate: 'string',
        auditor: 'string',
        status: 'string',
        findings: 'string',
        recommendations: 'string'
      },
      arrays: ['comments'],
      validValues: {
        type: ['interne', 'externe', 'certification', 'surveillance'],
        status: ['planifie', 'en_cours', 'realise', 'annule']
      }
    },

    kpi: {
      required: ['id', 'title'],
      types: {
        id: 'string',
        title: 'string',
        description: 'string',
        progress: 'number',
        comment: 'string'
      },
      ranges: {
        progress: { min: 0, max: 100 }
      }
    },

    review: {
      required: ['id', 'date'],
      types: {
        id: 'string',
        date: 'string',
        participants: 'string',
        inputs: 'string',
        decisions: 'string'
      },
      arrays: ['comments']
    },

    stakeholder: {
      required: ['id', 'name'],
      types: {
        id: 'string',
        name: 'string',
        role: 'string',
        contact: 'string'
      },
      arrays: ['comments']
    },

    document: {
      required: ['id'],
      types: {
        id: 'string',
        // Accept both 'level' (legacy) and 'category' (current)
        level: 'string',
        category: 'string',
        type: 'string',
        tool: 'string',
        link: 'string'
      },
      arrays: ['comments']
    },

    criticalAsset: {
      // FIX-ASSET-PRODUCTNAME (audit 2026-06-10) : productName est optionnel dans
      // l'UI (modale actifs critiques) — l'exiger ici bloquait toute la sauvegarde
      // dès qu'un actif était créé sans nom de produit.
      required: ['id'],
      types: {
        id: 'string',
        priority: 'number',
        beneficiary: 'string',
        productName: 'string',
        rto: 'string',
        rpo: 'string',
        availability: 'string',
        confidentiality: 'string',
        impact: 'string'
      }
    },

    projectRisk: {
      required: ['id', 'projectName'],
      types: {
        id: 'string',
        projectName: 'string',
        beneficiary: 'string',
        reportNumber: 'string',
        description: 'string',
        analysisLink: 'string',
        status: 'string'
      },
      arrays: ['recommendations']
    },

    riskAnalysis: {
      required: ['id', 'meta'],
      types: {
        id: 'string'
      },
      arrays: ['risks'],
      nested: {
        finalRecs: { arrays: ['items'] }
      }
    }
  };

  /**
   * Valide un item selon son schéma
   * @param {string} type - Type d'entité (action, risk, control, etc.)
   * @param {object} item - L'objet à valider
   * @returns {object} { valid: boolean, errors: string[], warnings: string[] }
   */
  function validateItem(type, item) {
    const schema = SCHEMAS[type];
    const result = { valid: true, errors: [], warnings: [] };

    if (!schema) {
      result.warnings.push(`Schéma inconnu pour le type: ${type}`);
      return result;
    }

    if (!item || typeof item !== 'object') {
      result.valid = false;
      result.errors.push(`L'item n'est pas un objet valide`);
      return result;
    }

    // Vérifier les champs requis
    if (schema.required) {
      for (const field of schema.required) {
        if (item[field] === undefined || item[field] === null || item[field] === '') {
          result.valid = false;
          result.errors.push(`Champ requis manquant: ${field}`);
        }
      }
    }

    // Vérifier les types
    if (schema.types) {
      for (const [field, expectedType] of Object.entries(schema.types)) {
        if (item[field] !== undefined && item[field] !== null) {
          const actualType = typeof item[field];
          if (actualType !== expectedType) {
            // Tolérance pour les nombres stockés comme strings
            if (expectedType === 'number' && actualType === 'string') {
              const parsed = parseFloat(item[field]);
              if (isNaN(parsed)) {
                result.warnings.push(`Type incorrect pour ${field}: attendu ${expectedType}, reçu ${actualType}`);
              }
            } else if (expectedType === 'string' && actualType === 'number') {
              // Acceptable, sera converti
            } else if (expectedType === 'stringOrArray') {
              if (actualType !== 'string' && !Array.isArray(item[field])) {
                result.warnings.push(`Type incorrect pour ${field}: attendu string ou tableau, reçu ${actualType}`);
              }
            } else {
              result.warnings.push(`Type incorrect pour ${field}: attendu ${expectedType}, reçu ${actualType}`);
            }
          }
        }
      }
    }

    // Vérifier les tableaux
    if (schema.arrays) {
      for (const field of schema.arrays) {
        if (item[field] !== undefined && !Array.isArray(item[field])) {
          result.warnings.push(`Le champ ${field} devrait être un tableau`);
        }
      }
    }

    // Vérifier les plages de valeurs
    if (schema.ranges) {
      for (const [field, range] of Object.entries(schema.ranges)) {
        const value = item[field];
        if (typeof value === 'number') {
          if (value < range.min || value > range.max) {
            result.warnings.push(`Valeur hors plage pour ${field}: ${value} (attendu ${range.min}-${range.max})`);
          }
        }
      }
    }

    // Vérifier les objets imbriqués
    if (schema.nested) {
      for (const [field, nestedSchema] of Object.entries(schema.nested)) {
        if (item[field] && typeof item[field] === 'object') {
          if (nestedSchema.arrays) {
            for (const arrField of nestedSchema.arrays) {
              if (item[field][arrField] !== undefined && !Array.isArray(item[field][arrField])) {
                result.warnings.push(`Le champ ${field}.${arrField} devrait être un tableau`);
              }
            }
          }
        }
      }
    }

    // Vérifier les valeurs autorisées
    if (schema.validValues) {
      for (const [field, allowedValues] of Object.entries(schema.validValues)) {
        const value = item[field];
        if (value !== undefined && value !== '' && !allowedValues.includes(value)) {
          result.warnings.push(`Valeur non standard pour ${field}: "${value}"`);
        }
      }
    }

    return result;
  }

  /**
   * Vérifie l'unicité des IDs dans un tableau
   * @param {array} items - Tableau d'items
   * @param {string} type - Type pour le message d'erreur
   * @returns {object} { valid: boolean, duplicates: string[] }
   */
  function checkIdUniqueness(items, type) {
    const ids = new Set();
    const duplicates = [];

    for (const item of items) {
      if (item.id) {
        if (ids.has(item.id)) {
          duplicates.push(item.id);
        } else {
          ids.add(item.id);
        }
      }
    }

    return {
      valid: duplicates.length === 0,
      duplicates
    };
  }

  /**
   * Vérifie les références croisées
   * @param {object} data - L'objet appData complet
   * @returns {array} Liste des références invalides
   */
  function checkCrossReferences(data) {
    const issues = [];

    // Collecter tous les IDs valides par type
    const validIds = {
      control: new Set((data.controls || []).map(c => c.id)),
      risk: new Set((data.risks || []).map(r => r.id)),
      threat: new Set((data.threats || []).map(t => t.id)),
      audit: new Set((data.audits || []).map(a => a.id)),
      action: new Set((data.actions || []).map(a => a.id)),
      nonconformity: new Set((data.nonconformities || []).map(nc => nc.id)),
      criticalAsset: new Set((data.criticalAssets || []).map(ca => ca.id))
    };

    // Vérifier les liens des actions
    for (const action of (data.actions || [])) {
      if (action.linkType && action.linkId) {
        const targetSet = validIds[action.linkType];
        if (targetSet) {
          if (Array.isArray(action.linkId)) {
            for (const lid of action.linkId) {
              if (!targetSet.has(lid)) {
                issues.push(`Action "${action.id}" référence un ${action.linkType} inexistant: "${lid}"`);
              }
            }
          } else if (!targetSet.has(action.linkId)) {
            issues.push(`Action "${action.id}" référence un ${action.linkType} inexistant: "${action.linkId}"`);
          }
        }
      }

      // Vérifier les actifs critiques liés
      if (Array.isArray(action.criticalAssetIds)) {
        for (const assetId of action.criticalAssetIds) {
          if (!validIds.criticalAsset.has(assetId)) {
            issues.push(`Action "${action.id}" référence un actif critique inexistant: "${assetId}"`);
          }
        }
      }
    }

    // Vérifier les actionIds dans les risques
    for (const risk of (data.risks || [])) {
      if (Array.isArray(risk.actionIds)) {
        for (const actionId of risk.actionIds) {
          if (!validIds.action.has(actionId)) {
            issues.push(`Risque "${risk.id}" référence une action inexistante: "${actionId}"`);
          }
        }
      }
    }

    // Vérifier les auditId dans les non-conformités
    for (const nc of (data.nonconformities || [])) {
      if (nc.auditId && !validIds.audit.has(nc.auditId)) {
        issues.push(`Non-conformité "${nc.id}" référence un audit inexistant: "${nc.auditId}"`);
      }
    }

    // Vérifier les linkedRiskIds dans les entrées SOA
    for (const entry of (data.soa || [])) {
      if (Array.isArray(entry.linkedRiskIds)) {
        for (const riskId of entry.linkedRiskIds) {
          if (!validIds.risk.has(riskId)) {
            issues.push(`SOA "${entry.controlId || entry.id}" référence un risque inexistant: "${riskId}"`);
          }
        }
      }
    }

    // Vérifier les ncIds dans les actions
    for (const action of (data.actions || [])) {
      if (Array.isArray(action.ncIds)) {
        for (const ncId of action.ncIds) {
          if (!validIds.nonconformity.has(ncId)) {
            issues.push(`Action "${action.id}" référence une non-conformité inexistante: "${ncId}"`);
          }
        }
      }
    }

    return issues;
  }

  /**
   * Valide l'ensemble des données avant sauvegarde
   * @param {object} data - L'objet appData complet
   * @returns {object} { valid: boolean, errors: string[], warnings: string[] }
   */
  function validateBeforeSave(data) {
    const result = {
      valid: true,
      errors: [],
      warnings: []
    };

    if (!data || typeof data !== 'object') {
      result.valid = false;
      result.errors.push('Données invalides: objet attendu');
      return result;
    }

    // Mapping des propriétés vers les types de schéma
    const collections = {
      controls: 'control',
      actions: 'action',
      risks: 'risk',
      threats: 'threat',
      nonconformities: 'nonconformity',
      audits: 'audit',
      kpis: 'kpi',
      reviews: 'review',
      stakeholders: 'stakeholder',
      documents: 'document',
      criticalAssets: 'criticalAsset',
      projectRisks: 'projectRisk',
      riskAnalyses: 'riskAnalysis'
    };

    // Valider chaque collection
    for (const [propName, schemaType] of Object.entries(collections)) {
      const items = data[propName];
      if (Array.isArray(items)) {
        // Vérifier l'unicité des IDs
        const uniqueCheck = checkIdUniqueness(items, propName);
        if (!uniqueCheck.valid) {
          result.warnings.push(`IDs dupliqués dans ${propName}: ${uniqueCheck.duplicates.join(', ')}`);
        }

        // Valider chaque item
        for (let i = 0; i < items.length; i++) {
          const itemResult = validateItem(schemaType, items[i]);
          if (!itemResult.valid) {
            result.errors.push(`${propName}[${i}]: ${itemResult.errors.join(', ')}`);
          }
          if (itemResult.warnings.length > 0) {
            // Limiter le nombre de warnings pour éviter le spam
            if (result.warnings.length < 20) {
              result.warnings.push(`${propName}[${i}]: ${itemResult.warnings.join(', ')}`);
            }
          }
        }
      }
    }

    // Vérifier les références croisées
    const crossRefIssues = checkCrossReferences(data);
    if (crossRefIssues.length > 0) {
      // Les références cassées sont des warnings, pas des erreurs bloquantes
      for (const issue of crossRefIssues.slice(0, 10)) {
        result.warnings.push(issue);
      }
      if (crossRefIssues.length > 10) {
        result.warnings.push(`... et ${crossRefIssues.length - 10} autres références invalides`);
      }
    }

    // Vérifier les champs obligatoires globaux
    if (!data.title || typeof data.title !== 'string') {
      result.warnings.push('Le titre de l\'application est manquant ou invalide');
    }

    if (typeof data.nextActionId !== 'number' || data.nextActionId < 0) {
      result.warnings.push('nextActionId invalide');
    }

    // Les erreurs rendent le résultat invalide
    if (result.errors.length > 0) {
      result.valid = false;
    }

    return result;
  }

  /**
   * Nettoie les données en appliquant les valeurs par défaut
   * @param {object} data - L'objet appData
   * @returns {object} Données nettoyées
   */
  function sanitizeData(data) {
    if (!data || typeof data !== 'object') return data;

    // Assurer que les tableaux existent
    const arrayProps = [
      'controls', 'actions', 'risks', 'threats', 'nonconformities',
      'audits', 'kpis', 'reviews', 'stakeholders', 'documents',
      'criticalAssets', 'projectRisks', 'nis2Controls', 'soa',
      'soclePillars', 'documentReview'
    ];

    for (const prop of arrayProps) {
      if (!Array.isArray(data[prop])) {
        data[prop] = [];
      }
    }

    // Assurer que les objets existent
    if (!data.actionsHistory || typeof data.actionsHistory !== 'object') {
      data.actionsHistory = {};
    }

    if (!data.targetMaturity || typeof data.targetMaturity !== 'object') {
      data.targetMaturity = { iso27002: 3, nis2: {}, nis2Target: 0 };
    }

    // Nettoyer les scores de risque
    for (const risk of data.risks) {
      if (typeof risk.impact === 'number' && typeof risk.probability === 'number') {
        risk.score = risk.impact * risk.probability;
      }
    }

    return data;
  }

  // Exposer les fonctions globalement
  window.validateItem = validateItem;
  window.validateBeforeSave = validateBeforeSave;
  window.sanitizeData = sanitizeData;
  window.CSI_SCHEMAS = SCHEMAS;

  // Log de chargement (mode développement)
  if (typeof console !== 'undefined' && console.log) {
    void 0;
  }

})();
