# Fichier: services/scoring_service.py (VERSION FINALE CORRIGÉE)

from typing import Dict, Any, List, Tuple, Optional
import html
import logging

logger = logging.getLogger(__name__)

# ========================================================================
# VALEURS AUTORISÉES (WHITELIST) - MISE À JOUR POUR CHECKBOXES
# ========================================================================

ALLOWED_VALUES = {
    'q_2fa': {'oui', 'non', 'n_a'},
    'q_filtrage_mail': {'outil_specialise', 'microsoft_base', 'aucun'},
    'q_edr': {'edr', 'antivirus', 'aucun'},
    'q_gestionnaire_mdp': {'oui', 'non'},
    
    # ✅ NOUVEAU : Checkboxes sauvegardes
    'q_sauvegarde_types': {'editeur_specialise', 'cloud', 'nas', 'bande', 'usb', 'aucune'},
    'q_architecture_editeur': {'hybride', 'multi_sites', 'full_cloud'},
    
    # ✅ ANCIEN (compatibilité)
    'q_sauvegarde_solution': {'specialise', 'serveur_dedie', 'lto_rdx', 'cloud', 'nas', 'disque_local', 'aucun'},
    
    'q_3211': {'oui', 'non', 'partiel'},
    
    # ✅ NOUVEAU : Checkboxes fréquence
    'q_frequence': {'journaliere', 'hebdomadaire', 'mensuelle', 'aucune'},
    
    'q_cloud_rgpd': {'oui', 'non', 'sans_cloud'},
    
    # ✅ NOUVEAU : Checkboxes OS
    'q_os_postes': {'recent', 'unsupported_soon', 'obsolete'},
    'q_os_serveurs': {'recent', 'unsupported_soon', 'obsolete'},
    
    'q_hyperviseur': {'oui', 'non', 'n_a'},
    'q_parefeu': {'ngfw_gere', 'routeur_base', 'aucun'},
    'q_vpn': {'vpn_gere', 'rdp_ouvert', 'aucun'},
    'q_phishing': {'trimestriel', 'annuel', 'jamais'},
    'q_formation': {'oui', 'non'},
    'q_pra_pca': {'teste', 'existe', 'inexistant'},
    'q_audit': {'oui', 'non'},
    'volumetrie': None,  # Texte libre (sera sanitizé)
}

# ========================================================================
# FONCTION UTILITAIRE DE SÉCURITÉ
# ========================================================================

def _get_safe_value(data: Dict[str, Any], key: str, default: Any, expected_type: type = str) -> Any:
    """
    Extrait une valeur de manière sécurisée avec validation whitelist
    """
    value = data.get(key, default)
    
    # Validation des nombres
    if expected_type == int:
        try:
            val = int(value)
            return max(0, min(val, 9999))  # Limite 0-9999
        except (ValueError, TypeError):
            return default
    
    # ✅ Support des listes (checkboxes)
    if expected_type == list:
        if not isinstance(value, list):
            # Si c'est une string (ancien format), la transformer en liste
            if isinstance(value, str) and value:
                value = [value]
            else:
                return default
        
        # Valider chaque élément de la liste
        if key in ALLOWED_VALUES:
            allowed = ALLOWED_VALUES[key]
            if allowed is not None:
                validated = [v for v in value if v in allowed]
                return validated if validated else default
        
        return value[:10]  # Max 10 éléments
    
    # Validation des strings
    if expected_type == str:
        if not isinstance(value, str):
            value = str(value) if value is not None else default
        
        # Vérifier la whitelist si elle existe pour cette clé
        if key in ALLOWED_VALUES:
            allowed = ALLOWED_VALUES[key]
            if allowed is not None:  # None = texte libre
                if value not in allowed:
                    logger.warning(f"Valeur non autorisée pour {key}: {value}")
                    return default
        
        # Limiter la longueur et échapper
        value = value[:200] if value else default
        return html.escape(value) if value else default
    
    return value

# ========================================================================
# CONSTANTES DE SCORING - ✅ CORRIGÉES POUR TOTAL = 100
# ========================================================================

# PROTECTION (25 pts)
MAX_SCORE_2FA = 10
MAX_SCORE_FILTRAGE_MAIL = 4
MAX_SCORE_EDR = 8
MAX_SCORE_GESTIONNAIRE_MDP = 3

# SAUVEGARDE (25 pts) ✅ RÉÉQUILIBRÉ
MAX_SCORE_SAUVEGARDE_TYPES = 12    # Types de sauvegardes
MAX_SCORE_3211 = 8                  # ✅ AJOUTÉ - Règle 3-2-1-1
MAX_SCORE_FREQUENCE = 5             # Fréquence des sauvegardes

# CLOUD (2 pts)
MAX_SCORE_CLOUD_RGPD = 2

# SYSTÈMES (14 pts)
MAX_SCORE_OS_POSTES = 6
MAX_SCORE_OS_SERVEURS = 6
MAX_SCORE_HYPERVISEUR = 2

# RÉSEAU (15 pts)
MAX_SCORE_PAREFEU = 8
MAX_SCORE_VPN = 7

# HUMAIN (19 pts) ✅ AJUSTÉ
MAX_SCORE_PHISHING = 5              # ✅ RÉDUIT de 6 à 5
MAX_SCORE_FORMATION = 5             # ✅ RÉDUIT de 6 à 5
MAX_SCORE_PRA_PCA = 5               # ✅ RÉDUIT de 6 à 5
MAX_SCORE_AUDIT = 4

# TOTAL = 25 + 25 + 2 + 14 + 15 + 19 = 100 ✅

SCORE_MAP_CONFIG = {
    'points_2fa': MAX_SCORE_2FA,
    'points_filtrage': MAX_SCORE_FILTRAGE_MAIL,
    'points_edr': MAX_SCORE_EDR,
    'points_gestionnaire_mdp': MAX_SCORE_GESTIONNAIRE_MDP,
    'points_sauvegarde_types': MAX_SCORE_SAUVEGARDE_TYPES,
    'points_3211': MAX_SCORE_3211,  # ✅ AJOUTÉ
    'points_frequence': MAX_SCORE_FREQUENCE,
    'points_cloud': MAX_SCORE_CLOUD_RGPD,
    'points_os_postes': MAX_SCORE_OS_POSTES,
    'points_os_serveurs': MAX_SCORE_OS_SERVEURS,
    'points_hyperviseur': MAX_SCORE_HYPERVISEUR,
    'points_parefeu': MAX_SCORE_PAREFEU,
    'points_vpn': MAX_SCORE_VPN,
    'points_phishing': MAX_SCORE_PHISHING,
    'points_formation': MAX_SCORE_FORMATION,
    'points_pra_pca': MAX_SCORE_PRA_PCA,
    'points_audit': MAX_SCORE_AUDIT
}

# ========================================================================
# GÉNÉRATION DES PRÉCONISATIONS
# ========================================================================

def _generer_preconisations(reponses: Dict[str, Any], scores: Dict[str, Any]) -> List[Dict[str, Any]]:
    """Génère les préconisations basées sur les scores"""
    preconisations = []
    
    # Données de contexte
    total_postes = _get_safe_value(reponses, 'nb_postes_fixes', 0, int) + \
                   _get_safe_value(reponses, 'nb_postes_portables', 0, int)
    volumetrie = _get_safe_value(reponses, 'volumetrie', 'Non spécifiée', str)
    
    # --- BLOC 2 : PROTECTION ---
    if scores['points_2fa'] is not None and scores['points_2fa'] < MAX_SCORE_2FA:
        preconisations.append({
            'titre': "Activer l'Authentification Multi-Facteur (MFA)",
            'priorite': 1, 'categorie': 'Protection', 'gravite': 'Critique',
            'details': "L'absence de MFA est la porte d'entrée n°1 des pirates. Activez-la sur tous les accès sensibles."
        })
        
    if scores['points_edr'] is not None and scores['points_edr'] < MAX_SCORE_EDR:
        gravite = 'Critique' if scores['points_edr'] == 0 else 'Élevée'
        preconisations.append({
            'titre': "Déployer une protection EDR avancée",
            'priorite': 1.5, 'categorie': 'Protection', 'gravite': gravite,
            'details': f"Protégez vos {str(total_postes)} postes contre les ransomwares avec une solution EDR."
        })

    if scores['points_filtrage'] is not None and scores['points_filtrage'] < MAX_SCORE_FILTRAGE_MAIL:
        preconisations.append({
            'titre': "Renforcer le filtrage des emails",
            'priorite': 3, 'categorie': 'Protection', 'gravite': 'Élevée',
            'details': "Installez une solution dédiée pour bloquer les menaces par mail."
        })
        
    if scores['points_gestionnaire_mdp'] is not None and scores['points_gestionnaire_mdp'] < MAX_SCORE_GESTIONNAIRE_MDP:
        preconisations.append({
            'titre': "Adopter un gestionnaire de mots de passe",
            'priorite': 4, 'categorie': 'Protection', 'gravite': 'Moyenne',
            'details': "Sécurisez vos mots de passe avec un coffre-fort numérique."
        })

    # --- BLOC 3 : SAUVEGARDE ---
    if scores['points_sauvegarde_types'] is not None and scores['points_sauvegarde_types'] < MAX_SCORE_SAUVEGARDE_TYPES:
        sauvegarde_types = _get_safe_value(reponses, 'q_sauvegarde_types', [], list)
        
        if 'aucune' in sauvegarde_types or not sauvegarde_types:
            preconisations.append({
                'titre': "🚨 URGENT : Mettre en place une sauvegarde",
                'priorite': 1, 'categorie': 'Sauvegarde', 'gravite': 'Critique',
                'details': f"Risque de perte totale des données ({volumetrie}). Installez une solution professionnelle immédiatement."
            })
        elif 'editeur_specialise' not in sauvegarde_types:
            preconisations.append({
                'titre': "Professionnaliser la sauvegarde avec un éditeur spécialisé",
                'priorite': 2, 'categorie': 'Sauvegarde', 'gravite': 'Élevée',
                'details': "Adoptez une solution dédiée pour une protection optimale."
            })
        
        # Recommandation diversification
        if 'cloud' not in sauvegarde_types and 'aucune' not in sauvegarde_types and 'editeur_specialise' not in sauvegarde_types:
            preconisations.append({
                'titre': "Ajouter une sauvegarde Cloud (hors site)",
                'priorite': 2.5, 'categorie': 'Sauvegarde', 'gravite': 'Élevée',
                'details': "Une sauvegarde hors site (cloud) protège contre les sinistres physiques (incendie, vol, ransomware)."
            })

    if scores['points_3211'] is not None and scores['points_3211'] < MAX_SCORE_3211:
        preconisations.append({
            'titre': "Respecter la règle 3-2-1-1",
            'priorite': 2, 'categorie': 'Sauvegarde', 'gravite': 'Élevée',
            'details': "3 copies des données, 2 types de supports, 1 copie hors site, 1 copie offline/immuable."
        })

    if scores['points_frequence'] is not None and scores['points_frequence'] < MAX_SCORE_FREQUENCE:
        frequences = _get_safe_value(reponses, 'q_frequence', [], list)
        if 'journaliere' not in frequences:
            preconisations.append({
                'titre': "Augmenter la fréquence des sauvegardes",
                'priorite': 2.5, 'categorie': 'Sauvegarde', 'gravite': 'Moyenne',
                'details': "Passez à une fréquence quotidienne pour minimiser la perte de données en cas d'incident."
            })

    if scores['points_cloud'] is not None and scores['points_cloud'] < MAX_SCORE_CLOUD_RGPD:
        preconisations.append({
            'titre': "Conformité RGPD de l'hébergement Cloud",
            'priorite': 3.5, 'categorie': 'Sauvegarde', 'gravite': 'Moyenne',
            'details': "Vérifiez que vos données cloud sont hébergées en UE pour respecter le RGPD."
        })

    # --- BLOC 4 : SYSTÈMES ---
    if scores['points_os_postes'] is not None and scores['points_os_postes'] < MAX_SCORE_OS_POSTES:
        os_postes = _get_safe_value(reponses, 'q_os_postes', [], list)
        if 'obsolete' in os_postes or 'unsupported_soon' in os_postes:
            gravite = 'Critique' if 'obsolete' in os_postes else 'Élevée'
            preconisations.append({
                'titre': "Mettre à jour les OS des postes",
                'priorite': 2 if gravite == 'Critique' else 2.5,
                'categorie': 'Systèmes', 'gravite': gravite,
                'details': "Remplacez ou mettez à jour les postes avec des OS obsolètes pour éviter les failles de sécurité."
            })

    if scores['points_os_serveurs'] is not None and scores['points_os_serveurs'] < MAX_SCORE_OS_SERVEURS:
        os_serveurs = _get_safe_value(reponses, 'q_os_serveurs', [], list)
        if 'obsolete' in os_serveurs or 'unsupported_soon' in os_serveurs:
            preconisations.append({
                'titre': "Migrer les serveurs obsolètes",
                'priorite': 1.5, 'categorie': 'Systèmes', 'gravite': 'Critique',
                'details': "Les serveurs en fin de vie sont des cibles prioritaires pour les attaquants. Migration urgente requise."
            })

    if scores['points_hyperviseur'] is not None and scores['points_hyperviseur'] < MAX_SCORE_HYPERVISEUR:
        preconisations.append({
            'titre': "Mettre à jour l'hyperviseur",
            'priorite': 3.5, 'categorie': 'Systèmes', 'gravite': 'Moyenne',
            'details': "Un hyperviseur non à jour compromet la sécurité de toutes les machines virtuelles."
        })

    # --- BLOC 5 : RÉSEAU ---
    if scores['points_parefeu'] is not None and scores['points_parefeu'] < MAX_SCORE_PAREFEU:
        preconisations.append({
            'titre': "Installer un pare-feu NGFW",
            'priorite': 2, 'categorie': 'Réseau', 'gravite': 'Élevée',
            'details': "Remplacez la box opérateur par un pare-feu professionnel nouvelle génération."
        })

    if scores['points_vpn'] is not None and scores['points_vpn'] < MAX_SCORE_VPN:
        preconisations.append({
            'titre': "🚨 CRITIQUE : Sécuriser les accès distants",
            'priorite': 1, 'categorie': 'Réseau', 'gravite': 'Critique',
            'details': "Fermez les ports RDP ouverts sur Internet et imposez un VPN pour tout accès à distance."
        })

    # --- BLOC 6 : HUMAIN ---
    if scores['points_phishing'] is not None and scores['points_phishing'] < MAX_SCORE_PHISHING:
        preconisations.append({
            'titre': "Lancer des campagnes de Phishing",
            'priorite': 3, 'categorie': 'Humain', 'gravite': 'Élevée',
            'details': "Testez et entraînez vos utilisateurs avec des simulations régulières."
        })

    if scores['points_formation'] is not None and scores['points_formation'] < MAX_SCORE_FORMATION:
        preconisations.append({
            'titre': "Former les utilisateurs à la cybersécurité",
            'priorite': 3, 'categorie': 'Humain', 'gravite': 'Élevée',
            'details': "La sensibilisation annuelle est indispensable face à l'évolution des menaces."
        })

    if scores['points_pra_pca'] is not None and scores['points_pra_pca'] < MAX_SCORE_PRA_PCA:
        preconisations.append({
            'titre': "Formaliser et tester un PRA/PCA",
            'priorite': 2, 'categorie': 'Humain', 'gravite': 'Élevée',
            'details': "Préparez votre entreprise à survivre et se relever rapidement d'un sinistre majeur."
        })

    if scores['points_audit'] is not None and scores['points_audit'] < MAX_SCORE_AUDIT:
        preconisations.append({
            'titre': "Réaliser un audit de sécurité externe",
            'priorite': 4, 'categorie': 'Humain', 'gravite': 'Faible',
            'details': "Faites valider votre niveau de sécurité par un auditeur indépendant."
        })

    return sorted(preconisations, key=lambda x: x['priorite'])

# ========================================================================
# CALCUL DES POINTS (AVEC GESTION CHECKBOXES)
# ========================================================================

def _calculer_points(reponses: Dict[str, Any]) -> Dict[str, Optional[int]]:
    """Calcule les points pour chaque catégorie. Retourne None pour les sections N/A."""
    scores = {}

    # ========================================================================
    # BLOC 2 : PROTECTION (25 pts)
    # ========================================================================
    
    scores['points_2fa'] = MAX_SCORE_2FA if _get_safe_value(reponses, 'q_2fa', '') == 'oui' else 0
    scores['points_filtrage'] = MAX_SCORE_FILTRAGE_MAIL if _get_safe_value(reponses, 'q_filtrage_mail', '') == 'outil_specialise' else 0
    
    edr = _get_safe_value(reponses, 'q_edr', '')
    scores['points_edr'] = MAX_SCORE_EDR if edr == 'edr' else (MAX_SCORE_EDR / 2 if edr == 'antivirus' else 0)
    
    scores['points_gestionnaire_mdp'] = MAX_SCORE_GESTIONNAIRE_MDP if _get_safe_value(reponses, 'q_gestionnaire_mdp', '') == 'oui' else 0

    # ========================================================================
    # BLOC 3 : SAUVEGARDE (25 pts) - ✅ NOUVEAU SCORING LOGIQUE
    # ========================================================================
    
    # Récupérer les types de sauvegardes (checkboxes)
    sauvegarde_types = _get_safe_value(reponses, 'q_sauvegarde_types', [], list)
    
    # Si c'est vide (compatibilité ancien format), essayer l'ancien champ
    if not sauvegarde_types:
        ancien_format = _get_safe_value(reponses, 'q_sauvegarde_solution', '', str)
        if ancien_format:
            mapping = {
                'specialise': 'editeur_specialise',
                'cloud': 'cloud',
                'nas': 'nas',
                'lto_rdx': 'bande',
                'disque_local': 'usb',
                'aucun': 'aucune'
            }
            sauvegarde_types = [mapping.get(ancien_format, ancien_format)]
    
    # ✅ CALCUL DU SCORE DE SAUVEGARDE (12 pts max)
    score_sauvegarde = 0
    
    if 'aucune' in sauvegarde_types or not sauvegarde_types:
        score_sauvegarde = 0
        
    elif 'editeur_specialise' in sauvegarde_types:
        # ✅ ÉDITEUR SPÉCIALISÉ = 12 PTS (peu importe l'archi)
        score_sauvegarde = 12
        
    else:
        # ✅ SOLUTIONS "BRICOLÉES" - SCORING INTELLIGENT
        has_cloud = 'cloud' in sauvegarde_types
        has_nas = 'nas' in sauvegarde_types
        has_bande = 'bande' in sauvegarde_types
        has_usb = 'usb' in sauvegarde_types
        
        # COMBINAISONS AVEC CLOUD (hors site = bien)
        if has_cloud and has_nas and has_bande:
            score_sauvegarde = 8  # Cloud + NAS + Bande
        elif has_cloud and has_nas:
            score_sauvegarde = 8  # Cloud + NAS
        elif has_cloud and has_bande:
            score_sauvegarde = 8  # Cloud + Bande
        elif has_cloud and has_usb:
            score_sauvegarde = 6  # Cloud + USB (pas ouf)
        elif has_cloud:
            score_sauvegarde = 6  # Cloud seul
            
        # COMBINAISONS SANS CLOUD (tout local = nul)
        elif has_nas and (has_bande or has_usb):
            score_sauvegarde = 5  # NAS + autre (nul)
        elif has_nas:
            score_sauvegarde = 5  # NAS seul
        elif has_bande:
            score_sauvegarde = 2  # Bande seule
        elif has_usb:
            score_sauvegarde = 2  # USB seule
    
    scores['points_sauvegarde_types'] = score_sauvegarde
    
    # ✅ RÈGLE 3-2-1-1 (8 pts)
    r321 = _get_safe_value(reponses, 'q_3211', '')
    scores['points_3211'] = MAX_SCORE_3211 if r321 == 'oui' else (MAX_SCORE_3211 / 2 if r321 == 'partiel' else 0)
    
    # ✅ FRÉQUENCE (5 pts) - On prend la MEILLEURE cochée
    frequences = _get_safe_value(reponses, 'q_frequence', [], list)
    
    # Compatibilité ancien format (string)
    if not frequences or not isinstance(frequences, list):
        ancien_freq = _get_safe_value(reponses, 'q_frequence', '', str)
        if ancien_freq:
            frequences = [ancien_freq]
    
    score_frequence = 0
    if 'aucune' not in frequences and frequences:
        if 'journaliere' in frequences:
            score_frequence = 5  # MAX
        elif 'hebdomadaire' in frequences:
            score_frequence = 3
        elif 'mensuelle' in frequences:
            score_frequence = 1
    
    scores['points_frequence'] = score_frequence
    
    # Cloud RGPD (N/A possible)
    cloud_rgpd = _get_safe_value(reponses, 'q_cloud_rgpd', '')
    if cloud_rgpd == 'sans_cloud':
        scores['points_cloud'] = None
    else:
        scores['points_cloud'] = MAX_SCORE_CLOUD_RGPD if cloud_rgpd == 'oui' else 0

    # ========================================================================
    # BLOC 4 : SYSTÈMES (14 pts) - ✅ OS PLUS DOUX
    # ========================================================================
    
    # OS Postes (checkboxes)
    os_postes = _get_safe_value(reponses, 'q_os_postes', [], list)
    
    # ✅ NOUVEAU : Si 0 postes, mettre N/A
    nb_postes = _get_safe_value(reponses, 'nb_postes_fixes', 0, int) + \
                _get_safe_value(reponses, 'nb_postes_portables', 0, int)
    
    if nb_postes == 0:
        scores['points_os_postes'] = None  # N/A
    else:
        # Compatibilité ancien format
        if not os_postes or not isinstance(os_postes, list):
            ancien_os_p = _get_safe_value(reponses, 'q_os_postes', '', str)
            if ancien_os_p:
                os_postes = [ancien_os_p]
        
        score_os_postes = 0
        if os_postes:
            if 'recent' in os_postes and len(os_postes) == 1:
                score_os_postes = MAX_SCORE_OS_POSTES  # Tous à jour = 6 pts
            elif 'recent' in os_postes:
                score_os_postes = 4  # ✅ Mixte = 4 pts
            elif 'unsupported_soon' in os_postes and 'obsolete' not in os_postes:
                score_os_postes = 2  # Fin de vie proche = 2 pts
            # obsolete = 0 points
        
        scores['points_os_postes'] = score_os_postes
    
    # OS Serveurs (checkboxes)
    os_serveurs = _get_safe_value(reponses, 'q_os_serveurs', [], list)
    
    # ✅ NOUVEAU : Si 0 serveurs, mettre N/A
    nb_serveurs = _get_safe_value(reponses, 'nb_serveurs_physiques', 0, int) + \
                  _get_safe_value(reponses, 'nb_serveurs_virtuels', 0, int)
    
    if nb_serveurs == 0:
        scores['points_os_serveurs'] = None  # N/A
    else:
        # Compatibilité ancien format
        if not os_serveurs or not isinstance(os_serveurs, list):
            ancien_os_s = _get_safe_value(reponses, 'q_os_serveurs', '', str)
            if ancien_os_s:
                os_serveurs = [ancien_os_s]
        
        score_os_serveurs = 0
        if os_serveurs:
            if 'recent' in os_serveurs and len(os_serveurs) == 1:
                score_os_serveurs = MAX_SCORE_OS_SERVEURS  # Tous à jour = 6 pts
            elif 'recent' in os_serveurs:
                score_os_serveurs = 4  # ✅ Mixte = 4 pts
            elif 'unsupported_soon' in os_serveurs and 'obsolete' not in os_serveurs:
                score_os_serveurs = 2  # Fin de vie proche = 2 pts
            # obsolete = 0 points
        
        scores['points_os_serveurs'] = score_os_serveurs
    
    # Hyperviseur (N/A possible)
    hyp = _get_safe_value(reponses, 'q_hyperviseur', '')
    
    # ✅ NOUVEAU : Si 0 serveurs, hyperviseur N/A
    if nb_serveurs == 0:
        scores['points_hyperviseur'] = None  # Pas de serveurs = pas d'hyperviseur
    elif hyp == 'n_a':
        scores['points_hyperviseur'] = None
    else:
        scores['points_hyperviseur'] = MAX_SCORE_HYPERVISEUR if hyp == 'oui' else 0

    # ========================================================================
    # BLOC 5 : RÉSEAU (15 pts)
    # ========================================================================
    
    fw = _get_safe_value(reponses, 'q_parefeu', '')
    scores['points_parefeu'] = MAX_SCORE_PAREFEU if fw == 'ngfw_gere' else (MAX_SCORE_PAREFEU / 2 if fw == 'routeur_base' else 0)
    
    # VPN (N/A possible)
    vpn = _get_safe_value(reponses, 'q_vpn', '')
    if vpn == 'aucun':
        scores['points_vpn'] = None
    else:
        scores['points_vpn'] = MAX_SCORE_VPN if vpn == 'vpn_gere' else 0

    # ========================================================================
    # BLOC 6 : HUMAIN (19 pts)
    # ========================================================================
    
    phish = _get_safe_value(reponses, 'q_phishing', '')
    scores['points_phishing'] = MAX_SCORE_PHISHING if phish == 'trimestriel' else (MAX_SCORE_PHISHING / 2 if phish == 'annuel' else 0)
    
    scores['points_formation'] = MAX_SCORE_FORMATION if _get_safe_value(reponses, 'q_formation', '') == 'oui' else 0
    
    pra = _get_safe_value(reponses, 'q_pra_pca', '')
    scores['points_pra_pca'] = MAX_SCORE_PRA_PCA if pra == 'teste' else (MAX_SCORE_PRA_PCA / 2 if pra == 'existe' else 0)
    
    scores['points_audit'] = MAX_SCORE_AUDIT if _get_safe_value(reponses, 'q_audit', '') == 'oui' else 0

    return scores

# ========================================================================
# FONCTION PRINCIPALE (CALCUL NEUTRE ABSOLU)
# ========================================================================

def calculer_cybernote_et_rapport(form_data: Dict[str, Any]) -> Tuple[int, Dict[str, Any]]:
    """Calcule le score total. Les N/A sont traités comme Neutres (Points acquis)."""
    
    # Validation
    if not isinstance(form_data, dict):
        raise ValueError("form_data doit être un dictionnaire")
    if len(form_data) > 100:
        raise ValueError("Trop de données dans le formulaire")
    
    # 1. Calcul des points par item
    scores = _calculer_points(form_data)
    
    # 2. Calcul du Total
    total_points_obtenus = 0
    total_points_possibles = 0
    
    for key, valeur in scores.items():
        max_theorique = SCORE_MAP_CONFIG.get(key, 0)
        
        # Gestion du N/A
        if valeur is None:
            # Cas N/A : On donne les points pour ne pas pénaliser le ratio
            total_points_obtenus += max_theorique
        else:
            # Cas normal
            if valeur > max_theorique:
                logger.warning(f"Score {key} dépasse le max: {valeur} > {max_theorique}")
                valeur = max_theorique
            total_points_obtenus += valeur
            
        # Le dénominateur inclut toujours tout le monde
        total_points_possibles += max_theorique
    
    # 3. Calcul du pourcentage
    if total_points_possibles > 0:
        score_final_sur_100 = int((total_points_obtenus / total_points_possibles) * 100)
    else:
        score_final_sur_100 = 0
    
    # Sécurité bornes
    score_final_sur_100 = max(0, min(100, score_final_sur_100))
    
    # 4. Génération des préconisations
    preconisations = _generer_preconisations(form_data, scores)
    
    # 5. Limite préconisations
    MAX_PRECONISATIONS = 50
    if len(preconisations) > MAX_PRECONISATIONS:
        preconisations = preconisations[:MAX_PRECONISATIONS]
    
    # Log
    logger.info(
        "Calcul scoring terminé (Mode Final Corrigé)",
        extra={
            'score_final': score_final_sur_100,
            'points_obtenus': total_points_obtenus,
            'points_possibles': total_points_possibles,
            'nb_preconisations': len(preconisations)
        }
    )
    
    rapport_complet = {
        "score_total": score_final_sur_100,
        "detail_scores": scores,
        "preconisations_detail": preconisations
    }
    
    return score_final_sur_100, rapport_complet