# Fichier: blueprints/auth.py (VERSION FINALE - CORRIGÉE)

from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, session
from flask_login import login_user, logout_user, login_required, current_user
import pyotp 
import io
import pyqrcode 
import re
import secrets
import json
from datetime import datetime, timezone, timedelta
from flask_mail import Message
from itsdangerous import URLSafeTimedSerializer, SignatureExpired, BadSignature

# Importations
from extensions import db, bcrypt, mail, limiter
from models import User 

auth = Blueprint('auth', __name__, template_folder='../templates')

# ========================================================================
# CONSTANTES DE SÉCURITÉ
# ========================================================================
DUMMY_PASSWORD_HASH = '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5jtRPPbkEfLGu'

# ========================================================================
# FONCTIONS DE VALIDATION
# ========================================================================

def validate_password_strength(password):
    if len(password) < 12: return False, "Le mot de passe doit contenir au moins 12 caractères."
    if not re.search(r'[A-Z]', password): return False, "Majuscule requise."
    if not re.search(r'[a-z]', password): return False, "Minuscule requise."
    if not re.search(r'\d', password): return False, "Chiffre requis."
    if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password): return False, "Caractère spécial requis."
    return True, "OK"

def validate_email_format(email):
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return re.match(pattern, email) is not None

# ========================================================================
# GESTION TOKENS 2FA
# ========================================================================

def create_2fa_token(user_id):
    s = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
    return s.dumps({'user_id': str(user_id)}, salt='2fa-verification')

def verify_2fa_token(token, max_age=300):
    s = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
    try:
        data = s.loads(token, salt='2fa-verification', max_age=max_age)
        return data.get('user_id')
    except (SignatureExpired, BadSignature):
        return None

# ========================================================================
# FONCTIONS EMAIL
# ========================================================================

def send_reset_email(user):
    token = user.get_reset_token(secret_key=str(current_app.config['SECRET_KEY']))
    msg = Message('Réinitialisation de mot de passe - MCyber Audit', recipients=[user.email])
    reset_url = url_for('auth.reset_password', token=token, _external=True)
    msg.body = f"""Bonjour,\n\nCliquez ici pour réinitialiser votre mot de passe :\n{reset_url}\n\nCe lien expire dans 30 minutes."""
    try: mail.send(msg)
    except Exception as e: current_app.logger.error(f"Erreur mail reset: {e}")

def send_password_changed_notification(user):
    try:
        msg = Message('Mot de passe modifié - MCyber Audit', recipients=[user.email])
        msg.body = f"Bonjour,\n\nVotre mot de passe a été modifié le {datetime.now(timezone.utc).strftime('%d/%m/%Y à %H:%M')} UTC."
        mail.send(msg)
    except Exception as e: current_app.logger.error(f"Erreur mail notif pass: {e}")

# ========================================================================
# ROUTES LOGIN / LOGOUT
# ========================================================================

@auth.route("/login", methods=['GET', 'POST'])
@limiter.limit("5 per minute")
def login():
    if current_user.is_authenticated:
        return redirect(url_for('main.dashboard'))

    if request.method == 'POST':
        email = request.form.get('email', '').strip().lower()
        password = request.form.get('password', '')
        
        if not email or not password:
            flash('Email et mot de passe requis.', 'danger')
            return render_template('login.html')
        
        user = User.query.filter_by(email=email).first()
        
        # Timing attack protection
        if user: is_valid = bcrypt.check_password_hash(user.password, password)
        else:
            bcrypt.check_password_hash(DUMMY_PASSWORD_HASH, password)
            is_valid = False
        
        if user and is_valid:
            if not user.is_active_account:
                flash('Compte désactivé.', 'danger')
                return render_template('login.html')

            # 2FA CHECK
            if user.two_factor_enabled:
                token = create_2fa_token(user.id)
                session['2fa_token'] = token
                return redirect(url_for('auth.verify_2fa'))
            
            login_user(user, remember=True)
            current_app.logger.info(f"✅ Login: {user.email}")
            
            # ✅ LOG ACTIVITÉ
            from services.logger_service import log_auth_success
            log_auth_success(user.id, user.email)
            
            return redirect(request.args.get('next') or url_for('main.dashboard'))
        else:
            current_app.logger.warning(f"❌ Login fail: {email}")
            
            # ✅ LOG ERREUR
            from services.logger_service import log_auth_failed
            log_auth_failed(email, "Identifiants invalides")
            
            flash('Email ou mot de passe invalide.', 'danger')

    return render_template('login.html')

@auth.route("/verify-2fa", methods=['GET', 'POST'])
@limiter.limit("15 per 5 minutes")
def verify_2fa():
    token = session.get('2fa_token')
    if not token: return redirect(url_for('auth.login'))
    
    user_id = verify_2fa_token(token)
    if not user_id:
        session.pop('2fa_token', None)
        flash('Session expirée.', 'warning')
        return redirect(url_for('auth.login'))
    
    user = User.query.get(user_id)
    if not user: return redirect(url_for('auth.login'))

    if request.method == 'POST':
        code_input = request.form.get('otp', '').strip()
        
        # 1. Vérification TOTP Classique
        totp = pyotp.TOTP(user.two_factor_secret)
        if totp.verify(code_input, valid_window=1):
            login_user(user, remember=True)
            session.pop('2fa_token', None)
            current_app.logger.info(f"✅ 2FA TOTP OK: {user.email}")
            return redirect(url_for('main.dashboard'))
        
        # 2. Vérification Code de Secours
        if user.two_factor_recovery_codes:
            try:
                recovery_codes = json.loads(user.two_factor_recovery_codes)
                
                for idx, hashed_code in enumerate(recovery_codes):
                    if bcrypt.check_password_hash(hashed_code, code_input):
                        recovery_codes.pop(idx)
                        user.two_factor_recovery_codes = json.dumps(recovery_codes)
                        db.session.commit()
                        
                        login_user(user, remember=True)
                        session.pop('2fa_token', None)
                        
                        current_app.logger.info(f"✅ 2FA RECOVERY OK: {user.email}")
                        flash(f"Code de secours utilisé. Il vous en reste {len(recovery_codes)}.", 'warning')
                        return redirect(url_for('main.dashboard'))
            except Exception as e:
                current_app.logger.error(f"Erreur vérification codes secours: {e}")

        flash('Code invalide.', 'danger')

    return render_template('verify_2fa.html')

@auth.route("/logout", methods=['POST'])
@login_required
def logout():
    # ✅ LOG ACTIVITÉ
    from services.logger_service import log_logout
    log_logout(current_user.id, current_user.email)
    
    logout_user()
    session.pop('2fa_token', None)
    flash('Vous êtes déconnecté.', 'info')
    return redirect(url_for('auth.login'))

# ========================================================================
# SETTINGS
# ========================================================================

@auth.route("/settings")
@login_required
def account_settings():
    return render_template('account_settings.html')

@auth.route("/settings/update_profile", methods=['POST'])
@login_required
def update_profile():
    company_name = request.form.get('company_name', '').strip()[:100]
    current_user.company_name = company_name
    db.session.commit()
    flash("Profil mis à jour.", 'success')
    return redirect(url_for('auth.account_settings'))

@auth.route("/settings/change_password", methods=['POST'])
@login_required
def change_password():
    current_pw = request.form.get('current_password', '')
    new_pw = request.form.get('new_password', '')
    confirm_pw = request.form.get('confirm_password', '')

    if not bcrypt.check_password_hash(current_user.password, current_pw):
        flash("Mot de passe actuel incorrect.", 'danger')
        return redirect(url_for('auth.account_settings'))

    if new_pw != confirm_pw:
        flash("Les mots de passe ne correspondent pas.", 'danger')
        return redirect(url_for('auth.account_settings'))
    
    is_valid, msg = validate_password_strength(new_pw)
    if not is_valid:
        flash(msg, 'danger')
        return redirect(url_for('auth.account_settings'))
    
    current_user.password = bcrypt.generate_password_hash(new_pw).decode('utf-8')
    db.session.commit()
    send_password_changed_notification(current_user)
    
    # ✅ LOG ACTIVITÉ
    from services.logger_service import log_password_changed
    log_password_changed(current_user.id, current_user.email)
    
    flash("Mot de passe mis à jour.", 'success')
    return redirect(url_for('auth.account_settings'))

# ========================================================================
# 2FA MANAGEMENT
# ========================================================================

@auth.route('/enable_2fa', methods=['GET', 'POST'])
@login_required
def enable_2fa():
    if current_user.two_factor_enabled: return redirect(url_for('auth.account_settings'))

    # Initialisation du secret si manquant
    if not current_user.two_factor_secret:
        current_user.two_factor_secret = pyotp.random_base32()
        db.session.commit()

    if request.method == 'POST':
        token = request.form.get('otp', '').strip()
        totp = pyotp.TOTP(current_user.two_factor_secret)
        
        if totp.verify(token, valid_window=1):
            # 1. Générer codes
            raw_codes = [secrets.token_hex(4).upper() for _ in range(10)]
            # 2. Hacher codes
            hashed_codes = [bcrypt.generate_password_hash(code).decode('utf-8') for code in raw_codes]
            
            # 3. Sauvegarder
            current_user.two_factor_recovery_codes = json.dumps(hashed_codes)
            current_user.two_factor_enabled = True
            db.session.commit()
            
            # ✅ LOG ACTIVITÉ
            from services.logger_service import log_2fa_enabled
            log_2fa_enabled(current_user.id, current_user.email)
            
            flash("2FA configurée avec succès. Sauvegardez ces codes maintenant !", 'success')
            return render_template('show_recovery_codes.html', codes=raw_codes)
        else:
            flash("Code incorrect.", 'danger')
    
    # Génération QR Code
    qr = pyqrcode.create(current_user.get_totp_uri())
    buffer = io.BytesIO()
    qr.svg(buffer, scale=5)
    
    # ✅ CORRECTION ICI : on passe secret_key au template pour l'affichage manuel
    return render_template('enable_2fa.html', 
                           qr_code=buffer.getvalue().decode('utf-8'),
                           secret_key=current_user.two_factor_secret)

@auth.route('/disable_2fa', methods=['GET', 'POST'])
@login_required
def disable_2fa():
    if not current_user.two_factor_enabled: 
        return redirect(url_for('auth.account_settings'))
    
    if request.method == 'POST':
        if bcrypt.check_password_hash(current_user.password, request.form.get('password', '')):
            current_user.two_factor_enabled = False
            current_user.two_factor_recovery_codes = None
            db.session.commit()
            
            # ✅ LOG ACTIVITÉ
            from services.logger_service import log_2fa_disabled
            log_2fa_disabled(current_user.id, current_user.email)
            
            flash("2FA désactivée.", 'success')
            return redirect(url_for('auth.account_settings'))
        else:
            flash("Mot de passe incorrect.", 'danger')
    
    return render_template('disable_2fa.html')

# ========================================================================
# RESET PASSWORD
# ========================================================================

@auth.route('/reset_password_request', methods=['GET', 'POST'])
@limiter.limit("10 per hour")
def reset_password_request():
    if current_user.is_authenticated: return redirect(url_for('main.dashboard'))
    
    if request.method == 'POST':
        email = request.form.get('email', '').strip().lower()
        user = User.query.filter_by(email=email).first()
        if user: send_reset_email(user)
        flash('Si le compte existe, un email a été envoyé.', 'info')
        return redirect(url_for('auth.login'))
    return render_template('reset_request.html')

@auth.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):
    if current_user.is_authenticated: return redirect(url_for('main.dashboard'))
    
    user = User.verify_reset_token(token, secret_key=str(current_app.config['SECRET_KEY']))
    if not user:
        flash('Lien invalide ou expiré.', 'warning')
        return redirect(url_for('auth.reset_password_request'))
    
    if request.method == 'POST':
        pw = request.form.get('password', '')
        if pw != request.form.get('confirm_password', ''):
            flash('Mots de passe différents.', 'danger')
            return render_template('reset_token.html', token=token)
        
        is_valid, msg = validate_password_strength(pw)
        if not is_valid:
            flash(msg, 'danger')
            return render_template('reset_token.html', token=token)
        
        user.password = bcrypt.generate_password_hash(pw).decode('utf-8')
        db.session.commit()
        flash('Mot de passe mis à jour.', 'success')
        return redirect(url_for('auth.login'))
    
    return render_template('reset_token.html', token=token)

    # ========================================================================
# WHITE-LABEL (LOGO + PRÉFÉRENCES)
# ========================================================================

import os
from werkzeug.utils import secure_filename

# Configuration uploads logo
UPLOAD_FOLDER_LOGOS = 'static/uploads/logos'
ALLOWED_LOGO_EXTENSIONS = {'png', 'jpg', 'jpeg'}
MAX_LOGO_SIZE = 2 * 1024 * 1024  # 2 MB

def allowed_logo_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_LOGO_EXTENSIONS

@auth.route('/settings/upload_logo', methods=['POST'])
@login_required
def upload_logo():
    """Upload logo personnalisé (managers uniquement)"""
    
    # Vérifier que c'est un manager
    if current_user.role not in ['manager', 'admin']:
        flash("❌ Seuls les managers peuvent uploader un logo.", 'danger')
        return redirect(url_for('auth.account_settings'))
    
    # Validation fichier
    if 'logo' not in request.files:
        flash("❌ Aucun fichier sélectionné.", 'danger')
        return redirect(url_for('auth.account_settings'))
    
    file = request.files['logo']
    
    if file.filename == '':
        flash("❌ Aucun fichier sélectionné.", 'danger')
        return redirect(url_for('auth.account_settings'))
    
    if not allowed_logo_file(file.filename):
        flash("❌ Format invalide. Formats acceptés : PNG, JPG, JPEG", 'danger')
        return redirect(url_for('auth.account_settings'))
    
    # Validation taille
    file.seek(0, os.SEEK_END)
    file_size = file.tell()
    file.seek(0)
    
    if file_size > MAX_LOGO_SIZE:
        flash("❌ Fichier trop volumineux (max 2 MB).", 'danger')
        return redirect(url_for('auth.account_settings'))
    
    # Création dossier si nécessaire
    upload_path = os.path.join(current_app.root_path, UPLOAD_FOLDER_LOGOS)
    os.makedirs(upload_path, exist_ok=True)
    
    # Supprimer ancien logo si existe
    if current_user.logo_filename:
        old_logo_path = os.path.join(upload_path, current_user.logo_filename)
        try:
            if os.path.exists(old_logo_path):
                os.remove(old_logo_path)
        except Exception as e:
            current_app.logger.warning(f"Impossible de supprimer ancien logo: {e}")
    
    # Génération nom unique
    import uuid
    file_extension = file.filename.rsplit('.', 1)[1].lower()
    unique_filename = f"{uuid.uuid4().hex}.{file_extension}"
    file_path = os.path.join(upload_path, unique_filename)
    
    # Sauvegarde fichier
    try:
        file.save(file_path)
    except Exception as e:
        current_app.logger.error(f"Erreur sauvegarde logo: {e}", exc_info=True)
        flash("❌ Erreur lors de l'upload.", 'danger')
        return redirect(url_for('auth.account_settings'))
    
    # Mise à jour DB
    current_user.logo_filename = unique_filename
    
    try:
        db.session.commit()
        current_app.logger.info(f"🎨 {current_user.email} upload logo: {unique_filename}")
        
        # ✅ LOG ACTIVITÉ
        from services.logger_service import log_logo_uploaded
        log_logo_uploaded(current_user.id, current_user.email)
        
        flash("✅ Logo uploadé avec succès !", 'success')
    except Exception as e:
        db.session.rollback()
        # Supprimer le fichier en cas d'erreur DB
        try:
            os.remove(file_path)
        except:
            pass
        current_app.logger.error(f"Erreur DB upload logo: {e}", exc_info=True)
        flash("❌ Erreur lors de l'enregistrement.", 'danger')
    
    return redirect(url_for('auth.account_settings'))

@auth.route('/settings/delete_logo', methods=['POST'])
@login_required
def delete_logo():
    """Supprimer le logo personnalisé"""
    
    if not current_user.logo_filename:
        flash("❌ Aucun logo à supprimer.", 'danger')
        return redirect(url_for('auth.account_settings'))
    
    # Supprimer fichier physique
    upload_path = os.path.join(current_app.root_path, UPLOAD_FOLDER_LOGOS)
    logo_path = os.path.join(upload_path, current_user.logo_filename)
    
    try:
        if os.path.exists(logo_path):
            os.remove(logo_path)
    except Exception as e:
        current_app.logger.error(f"Erreur suppression fichier logo: {e}", exc_info=True)
    
    # Mise à jour DB
    current_user.logo_filename = None
    current_user.show_logo_on_reports = False
    
    try:
        db.session.commit()
        current_app.logger.info(f"🗑️ {current_user.email} supprime son logo")
        flash("✅ Logo supprimé.", 'success')
    except Exception as e:
        db.session.rollback()
        current_app.logger.error(f"Erreur DB suppression logo: {e}", exc_info=True)
        flash("❌ Erreur lors de la suppression.", 'danger')
    
    return redirect(url_for('auth.account_settings'))

@auth.route('/settings/update_branding', methods=['POST'])
@login_required
def update_branding():
    """Mettre à jour les préférences white-label"""
    
    if current_user.role == 'manager' or current_user.role == 'admin':
        # Managers : checkboxes logo + nom entreprise
        current_user.show_logo_on_reports = 'show_logo' in request.form
        current_user.show_company_name_on_reports = 'show_company_name' in request.form
    
    elif current_user.role == 'user':
        # Users : checkbox "utiliser branding manager"
        current_user.use_manager_branding = 'use_manager_branding' in request.form
    
    try:
        db.session.commit()
        current_app.logger.info(f"🎨 {current_user.email} modifie préférences white-label")
        flash("✅ Préférences mises à jour.", 'success')
    except Exception as e:
        db.session.rollback()
        current_app.logger.error(f"Erreur update branding: {e}", exc_info=True)
        flash("❌ Erreur lors de la mise à jour.", 'danger')
    
    return redirect(url_for('auth.account_settings'))