<?php

declare(strict_types=1);

namespace App\Controllers;

require_once __DIR__ . '/../Services/LogService.php';
require_once __DIR__ . '/../Services/EventLogService.php';
require_once __DIR__ . '/../Services/ApiService.php';
require_once __DIR__ . '/../Services/PasswordSecurityService.php';

use PDO;
use PDOException;
use Database;
use AppConfig;
use Request;
use Response;
use Session;
use App\Services\LogService;
use App\Services\EventLogService;
use App\Services\ApiService;
use App\Services\PasswordSecurityService;

class UserController {
    private PDO $db;
    private AppConfig $appConfig;

    public function __construct() {
        $this->db = Database::getInstance();
        $this->appConfig = AppConfig::getInstance();
    }

    public function getUsers(): void {
        Session::requireAuth();

        // Vérification des droits d'accès (rôle administrateur)
        // Récupérer le rôle de l'utilisateur depuis la base de données
        $userId = Session::getUserId();
        $stmt = $this->db->prepare('SELECT fk_role FROM users WHERE id = ?');
        $stmt->execute([$userId]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        $userRole = $result ? $result['fk_role'] : null;

        if ($userRole != '1' && $userRole != '2') {  // Supposons que 1 et 2 sont des rôles admin
            Response::json([
                'status' => 'error',
                'message' => 'Accès non autorisé'
            ], 403);
            return;
        }

        try {
            $stmt = $this->db->prepare('
                SELECT 
                    u.id, 
                    u.encrypted_email, 
                    u.encrypted_name, 
                    u.first_name, 
                    u.fk_role as role,
                    u.fk_entite,
                    u.chk_active,
                    u.created_at,
                    u.updated_at,
                    e.encrypted_name as entite_name
                FROM users u
                LEFT JOIN entites e ON u.fk_entite = e.id
                ORDER BY u.created_at DESC
            ');
            $stmt->execute();
            $users = $stmt->fetchAll(PDO::FETCH_ASSOC);

            // Déchiffrement des données sensibles pour chaque utilisateur
            foreach ($users as &$user) {
                $user['email'] = ApiService::decryptSearchableData($user['encrypted_email']);
                $user['name'] = ApiService::decryptData($user['encrypted_name']);

                if (!empty($user['entite_name'])) {
                    $user['entite_name'] = ApiService::decryptData($user['entite_name']);
                }

                // Suppression des champs chiffrés
                unset($user['encrypted_email']);
                unset($user['encrypted_name']);
            }

            Response::json([
                'status' => 'success',
                'users' => $users
            ]);
        } catch (PDOException $e) {
            LogService::log('Erreur lors de la récupération des utilisateurs GeoSector', [
                'level' => 'error',
                'error' => $e->getMessage()
            ]);
            Response::json([
                'status' => 'error',
                'message' => 'Erreur serveur'
            ], 500);
        }
    }

    public function getUserById(string $id): void {
        Session::requireAuth();

        // Vérification des droits d'accès (rôle administrateur ou utilisateur lui-même)
        $currentUserId = Session::getUserId();

        // Récupérer le rôle de l'utilisateur depuis la base de données
        $stmt = $this->db->prepare('SELECT fk_role FROM users WHERE id = ?');
        $stmt->execute([$currentUserId]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        $userRole = $result ? $result['fk_role'] : null;

        if ($userRole != '1' && $userRole != '2' && $currentUserId != $id) {
            Response::json([
                'status' => 'error',
                'message' => 'Accès non autorisé'
            ], 403);
            return;
        }

        try {
            $stmt = $this->db->prepare('
                SELECT 
                    u.id, 
                    u.encrypted_email, 
                    u.encrypted_name, 
                    u.first_name, 
                    u.sect_name,
                    u.encrypted_phone, 
                    u.encrypted_mobile, 
                    u.fk_role as role,
                    u.fk_entite,
                    u.chk_alert_email,
                    u.chk_suivi,
                    u.date_naissance,
                    u.date_embauche,
                    u.chk_active,
                    u.created_at,
                    u.updated_at,
                    e.encrypted_name as entite_name,
                    e.adresse1,
                    e.adresse2,
                    e.cp,
                    e.ville,
                    e.fk_region
                FROM users u
                LEFT JOIN entites e ON u.fk_entite = e.id
                WHERE u.id = ?
            ');
            $stmt->execute([$id]);
            $user = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$user) {
                Response::json([
                    'status' => 'error',
                    'message' => 'Utilisateur non trouvé'
                ], 404);
                return;
            }

            // Déchiffrement des données sensibles
            $user['email'] = ApiService::decryptSearchableData($user['encrypted_email']);
            $user['name'] = ApiService::decryptData($user['encrypted_name']);
            $user['phone'] = ApiService::decryptData($user['encrypted_phone'] ?? '');
            $user['mobile'] = ApiService::decryptData($user['encrypted_mobile'] ?? '');

            if (!empty($user['entite_name'])) {
                $user['entite_name'] = ApiService::decryptData($user['entite_name']);
            }

            // Suppression des champs chiffrés
            unset($user['encrypted_email']);
            unset($user['encrypted_name']);
            unset($user['encrypted_phone']);
            unset($user['encrypted_mobile']);

            Response::json([
                'status' => 'success',
                'user' => $user
            ]);
        } catch (PDOException $e) {
            LogService::log('Erreur lors de la récupération de l\'utilisateur GeoSector', [
                'level' => 'error',
                'error' => $e->getMessage(),
                'userId' => $id
            ]);
            Response::json([
                'status' => 'error',
                'message' => 'Erreur serveur'
            ], 500);
        }
    }

    public function createUser(): void {
        Session::requireAuth();

        // Vérification des droits d'accès (rôle administrateur)
        $currentUserId = Session::getUserId();

        // Récupérer le rôle de l'utilisateur depuis la base de données
        $stmt = $this->db->prepare('SELECT fk_role FROM users WHERE id = ?');
        $stmt->execute([$currentUserId]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        $userRole = $result ? $result['fk_role'] : null;

        if ($userRole != '1' && $userRole != '2') {
            Response::json([
                'status' => 'error',
                'message' => 'Accès non autorisé'
            ], 403);
            return;
        }

        try {
            $data = Request::getJson();
            $currentUserId = Session::getUserId();

            // Log de début de création avec les données reçues (sans données sensibles)
            LogService::log('Tentative de création d\'utilisateur', [
                'level' => 'debug',
                'createdBy' => $currentUserId,
                'fields_received' => array_keys($data ?? []),
                'has_email' => isset($data['email']),
                'has_name' => isset($data['name']),
                'has_username' => isset($data['username']),
                'has_password' => isset($data['password'])
            ]);

            // Validation : au moins name OU sect_name requis
            if ((!isset($data['name']) || empty(trim($data['name']))) &&
                (!isset($data['sect_name']) || empty(trim($data['sect_name'])))) {
                LogService::log('Erreur création utilisateur : Aucun nom fourni', [
                    'level' => 'warning',
                    'createdBy' => $currentUserId,
                    'email' => $data['email'] ?? 'non fourni',
                    'has_name' => isset($data['name']),
                    'has_sect_name' => isset($data['sect_name'])
                ]);
                Response::json([
                    'status' => 'error',
                    'message' => 'Au moins un nom (name ou sect_name) est requis',
                    'field' => 'name_or_sect_name'
                ], 400);
                return;
            }

            // L'email est maintenant optionnel
            $email = isset($data['email']) && !empty(trim($data['email']))
                ? trim(strtolower($data['email']))
                : '';
            // Le name peut être vide si sect_name est fourni
            $name = isset($data['name']) && !empty(trim($data['name']))
                ? trim($data['name'])
                : '';
            $firstName = isset($data['first_name']) ? trim($data['first_name']) : '';
            $role = isset($data['role']) ? (int)$data['role'] : 1;
            $entiteId = isset($data['fk_entite']) ? (int)$data['fk_entite'] : 1;

            // Récupérer les paramètres de gestion de l'entité
            $entiteStmt = $this->db->prepare('
                SELECT chk_mdp_manuel, chk_username_manuel, code_postal, ville 
                FROM entites 
                WHERE id = ?
            ');
            $entiteStmt->execute([$entiteId]);
            $entiteConfig = $entiteStmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$entiteConfig) {
                Response::json([
                    'status' => 'error',
                    'message' => 'Entité non trouvée'
                ], 404);
                return;
            }

            $chkMdpManuel = (int)$entiteConfig['chk_mdp_manuel'];
            $chkUsernameManuel = (int)$entiteConfig['chk_username_manuel'];

            // Vérification des longueurs d'entrée
            if (strlen($email) > 75 || strlen($name) > 50) {
                Response::json([
                    'status' => 'error',
                    'message' => 'Email ou nom trop long'
                ], 400);
                return;
            }

            // Validation de l'email seulement s'il est fourni
            if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
                LogService::log('Erreur création utilisateur : Format email invalide', [
                    'level' => 'warning',
                    'createdBy' => $currentUserId,
                    'email' => $email
                ]);
                Response::json([
                    'status' => 'error',
                    'message' => 'Format d\'email invalide',
                    'field' => 'email',
                    'value' => $email
                ], 400);
                return;
            }

            // Chiffrement des données sensibles
            // Chiffrer l'email seulement s'il n'est pas vide
            $encryptedEmail = !empty($email) ? ApiService::encryptSearchableData($email) : null;
            // Chiffrer le name seulement s'il n'est pas vide
            $encryptedName = !empty($name) ? ApiService::encryptData($name) : null;

            // Vérification de l'existence de l'email
            // DÉSACTIVÉ : Le client souhaite permettre plusieurs comptes avec le même email
            /*
            $checkStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_email = ?');
            $checkStmt->execute([$encryptedEmail]);
            if ($checkStmt->fetch()) {
                Response::json([
                    'status' => 'error',
                    'message' => 'Cet email est déjà utilisé'
                ], 409);
                return;
            }
            */

            // Gestion du USERNAME selon chk_username_manuel
            $encryptedUsername = '';
            if ($chkUsernameManuel === 1) {
                // Username manuel obligatoire
                if (!isset($data['username']) || empty(trim($data['username']))) {
                    LogService::log('Erreur création utilisateur : Username manuel requis mais non fourni', [
                        'level' => 'warning',
                        'createdBy' => $currentUserId,
                        'email' => $email,
                        'entite_id' => $entiteId,
                        'chk_username_manuel' => $chkUsernameManuel
                    ]);
                    Response::json([
                        'status' => 'error',
                        'message' => 'Identifiant requis',
                        'field' => 'username',
                        'details' => 'Saisie manuelle obligatoire pour cette entité'
                    ], 400);
                    return;
                }
                
                // Trim du username mais on garde la casse originale (plus de lowercase forcé)
                $username = trim($data['username']);
                
                // Validation ultra-souple : seulement la longueur en caractères UTF-8
                $usernameLength = mb_strlen($username, 'UTF-8');
                
                if ($usernameLength < 8) {
                    LogService::log('Erreur création utilisateur : Username trop court', [
                        'level' => 'warning',
                        'createdBy' => $currentUserId,
                        'email' => $email,
                        'username_length' => $usernameLength
                    ]);
                    Response::json([
                        'status' => 'error',
                        'message' => 'Identifiant trop court',
                        'field' => 'username',
                        'details' => 'Minimum 8 caractères'
                    ], 400);
                    return;
                }
                
                if ($usernameLength > 30) {
                    LogService::log('Erreur création utilisateur : Username trop long', [
                        'level' => 'warning',
                        'createdBy' => $currentUserId,
                        'email' => $email,
                        'username_length' => $usernameLength
                    ]);
                    Response::json([
                        'status' => 'error',
                        'message' => 'Identifiant trop long',
                        'field' => 'username',
                        'details' => 'Maximum 30 caractères'
                    ], 400);
                    return;
                }
                
                $encryptedUsername = ApiService::encryptSearchableData($username);
                
                // Vérification de l'unicité du username
                $checkUsernameStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_user_name = ?');
                $checkUsernameStmt->execute([$encryptedUsername]);
                if ($checkUsernameStmt->fetch()) {
                    Response::json([
                        'status' => 'error',
                        'message' => 'Identifiant déjà utilisé',
                        'field' => 'username'
                    ], 409);
                    return;
                }
            } else {
                // Génération automatique du username
                $username = ApiService::generateUserName(
                    $this->db, 
                    $name, 
                    $entiteConfig['code_postal'] ?? '00000', 
                    $entiteConfig['ville'] ?? 'ville',
                    10
                );
                $encryptedUsername = ApiService::encryptSearchableData($username);
            }

            // Gestion du MOT DE PASSE selon chk_mdp_manuel
            $password = '';
            $passwordHash = '';
            if ($chkMdpManuel === 1) {
                // Mot de passe manuel obligatoire
                if (!isset($data['password']) || empty($data['password'])) {
                    LogService::log('Erreur création utilisateur : Mot de passe manuel requis mais non fourni', [
                        'level' => 'warning',
                        'createdBy' => $currentUserId,
                        'email' => $email,
                        'entite_id' => $entiteId,
                        'chk_mdp_manuel' => $chkMdpManuel
                    ]);
                    Response::json([
                        'status' => 'error',
                        'message' => 'Mot de passe requis',
                        'field' => 'password',
                        'details' => 'Saisie manuelle obligatoire pour cette entité'
                    ], 400);
                    return;
                }
                
                $password = $data['password'];
                
                // Validation du mot de passe selon NIST SP 800-63B
                $passwordValidation = PasswordSecurityService::validatePassword($password);
                
                if (!$passwordValidation['valid']) {
                    Response::json([
                        'status' => 'error',
                        'message' => 'Mot de passe invalide',
                        'errors' => $passwordValidation['errors'],
                        'warnings' => $passwordValidation['warnings']
                    ], 400);
                    return;
                }
                
                // Si le mot de passe a des avertissements mais est valide, les logger
                if (!empty($passwordValidation['warnings'])) {
                    LogService::log('Mot de passe manuel avec avertissements accepté lors de la création', [
                        'level' => 'warning',
                        'email' => $email,
                        'warnings' => $passwordValidation['warnings']
                    ]);
                }
                
                $passwordHash = password_hash($password, PASSWORD_DEFAULT);
            } else {
                // Génération automatique du mot de passe (déjà vérifié contre HIBP)
                $password = ApiService::generateSecurePassword();
                $passwordHash = password_hash($password, PASSWORD_DEFAULT);
            }

            // Préparation des champs optionnels
            $phone = isset($data['phone']) ? ApiService::encryptData(trim($data['phone'])) : null;
            $mobile = isset($data['mobile']) ? ApiService::encryptData(trim($data['mobile'])) : null;
            $sectName = isset($data['sect_name']) ? trim($data['sect_name']) : '';
            $alertEmail = isset($data['chk_alert_email']) ? (int)$data['chk_alert_email'] : 1;
            $suivi = isset($data['chk_suivi']) ? (int)$data['chk_suivi'] : 0;
            $dateNaissance = isset($data['date_naissance']) ? $data['date_naissance'] : null;
            $dateEmbauche = isset($data['date_embauche']) ? $data['date_embauche'] : null;

            // Insertion en base de données
            $stmt = $this->db->prepare('
                INSERT INTO users (
                    encrypted_email, encrypted_user_name, user_pass_hash, encrypted_name, first_name, 
                    sect_name, encrypted_phone, encrypted_mobile, fk_role, 
                    fk_entite, chk_alert_email, chk_suivi, 
                    date_naissance, date_embauche,
                    created_at, fk_user_creat, chk_active
                ) VALUES (
                    ?, ?, ?, ?, ?, 
                    ?, ?, ?, ?, 
                    ?, ?, ?, 
                    ?, ?, 
                    NOW(), ?, 1
                )
            ');
            $stmt->execute([
                $encryptedEmail,
                $encryptedUsername,
                $passwordHash,
                $encryptedName,
                $firstName,
                $sectName,
                $phone,
                $mobile,
                $role,
                $entiteId,
                $alertEmail,
                $suivi,
                $dateNaissance,
                $dateEmbauche,
                $currentUserId
            ]);
            $userId = $this->db->lastInsertId();

            // Envoi des emails séparés pour plus de sécurité (seulement si un email est fourni)
            if (!empty($email)) {
                // 1er email : Envoyer l'identifiant (username)
                $usernameEmailData = [
                    'email' => $email,
                    'username' => $username,
                    'name' => $name
                ];
                ApiService::sendEmail($email, $name, 'welcome_username', $usernameEmailData);

                // 2ème email : Envoyer le mot de passe (toujours, qu'il soit manuel ou généré)
                // Attendre un peu entre les deux emails pour éviter qu'ils arrivent dans le mauvais ordre
                sleep(1);

                $passwordEmailData = [
                    'email' => $email,
                    'password' => $password,
                    'name' => $name
                ];
                ApiService::sendEmail($email, $name, 'welcome_password', $passwordEmailData);
            } else {
                LogService::log('Utilisateur créé sans email - pas d\'envoi de credentials', [
                    'level' => 'info',
                    'userId' => $userId,
                    'username' => $username
                ]);
            }

            // Log de création utilisateur
            EventLogService::logUserCreated(
                (int)$userId,
                (int)$entiteId,
                (int)$role,
                $username
            );

            // Préparer la réponse avec les informations de connexion si générées automatiquement
            $responseData = [
                'status' => 'success',
                'message' => 'Utilisateur créé avec succès',
                'id' => $userId
            ];
            
            // Ajouter le username dans la réponse (toujours, car nécessaire pour la connexion)
            $responseData['username'] = $username;
            
            // Ajouter le mot de passe seulement si généré automatiquement
            if ($chkMdpManuel === 0) {
                $responseData['password'] = $password;
            }

            Response::json($responseData, 201);
        } catch (PDOException $e) {
            LogService::log('Erreur lors de la création d\'un utilisateur GeoSector', [
                'level' => 'error',
                'error' => $e->getMessage(),
                'code' => $e->getCode()
            ]);
            Response::json([
                'status' => 'error',
                'message' => 'Erreur serveur'
            ], 500);
        }
    }

    public function updateUser(string $id): void {
        Session::requireAuth();

        // Vérification des droits d'accès (rôle administrateur ou utilisateur lui-même)
        $currentUserId = Session::getUserId();

        // Récupérer le rôle de l'utilisateur depuis la base de données
        $stmt = $this->db->prepare('SELECT fk_role FROM users WHERE id = ?');
        $stmt->execute([$currentUserId]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        $userRole = $result ? $result['fk_role'] : null;

        if ($userRole != '1' && $userRole != '2' && $currentUserId != $id) {
            Response::json([
                'status' => 'error',
                'message' => 'Accès non autorisé'
            ], 403);
            return;
        }

        try {
            $data = Request::getJson();

            // Vérification qu'il y a des données à mettre à jour
            if (empty($data)) {
                Response::json([
                    'status' => 'error',
                    'message' => 'Aucune donnée à mettre à jour'
                ], 400);
                return;
            }

            // Construction de la requête UPDATE dynamique
            $updateFields = [];
            $params = ['id' => $id];

            // Traitement des champs à chiffrer
            if (isset($data['email'])) {
                // Vérification que l'email n'est pas déjà utilisé par un autre utilisateur
                $email = trim(strtolower($data['email']));
                $encryptedEmail = ApiService::encryptSearchableData($email);

                // Vérification de l'unicité de l'email
                // DÉSACTIVÉ : Le client souhaite permettre plusieurs comptes avec le même email
                /*
                $checkStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_email = ? AND id != ?');
                $checkStmt->execute([$encryptedEmail, $id]);
                if ($checkStmt->fetch()) {
                    Response::json([
                        'status' => 'error',
                        'message' => 'Cet email est déjà utilisé par un autre utilisateur'
                    ], 409);
                    return;
                }
                */

                $updateFields[] = "encrypted_email = :encrypted_email";
                $params['encrypted_email'] = $encryptedEmail;
            }

            if (isset($data['name'])) {
                $updateFields[] = "encrypted_name = :encrypted_name";
                $params['encrypted_name'] = ApiService::encryptData(trim($data['name']));
            }

            if (isset($data['phone'])) {
                $updateFields[] = "encrypted_phone = :encrypted_phone";
                $params['encrypted_phone'] = ApiService::encryptData(trim($data['phone']));
            }

            if (isset($data['mobile'])) {
                $updateFields[] = "encrypted_mobile = :encrypted_mobile";
                $params['encrypted_mobile'] = ApiService::encryptData(trim($data['mobile']));
            }

            // Gestion de la modification du username
            if (isset($data['username'])) {
                $username = trim($data['username']);

                // Validation de la longueur en caractères UTF-8
                $usernameLength = mb_strlen($username, 'UTF-8');

                if ($usernameLength < 8) {
                    Response::json([
                        'status' => 'error',
                        'message' => 'Identifiant trop court',
                        'field' => 'username',
                        'details' => 'Minimum 8 caractères'
                    ], 400);
                    return;
                }

                if ($usernameLength > 30) {
                    Response::json([
                        'status' => 'error',
                        'message' => 'Identifiant trop long',
                        'field' => 'username',
                        'details' => 'Maximum 30 caractères'
                    ], 400);
                    return;
                }

                $encryptedUsername = ApiService::encryptSearchableData($username);

                // Vérifier l'unicité du username (sauf pour l'utilisateur courant)
                $checkStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_user_name = ? AND id != ?');
                $checkStmt->execute([$encryptedUsername, $id]);
                if ($checkStmt->fetch()) {
                    Response::json([
                        'status' => 'error',
                        'message' => 'Cet identifiant est déjà utilisé par un autre utilisateur',
                        'field' => 'username'
                    ], 409);
                    return;
                }

                $updateFields[] = "encrypted_user_name = :encrypted_user_name";
                $params['encrypted_user_name'] = $encryptedUsername;

                LogService::log('Modification du username', [
                    'level' => 'info',
                    'userId' => $id,
                    'newUsername' => $username,
                    'modifiedBy' => $currentUserId
                ]);
            }

            // Traitement des champs non chiffrés
            $nonEncryptedFields = [
                'first_name',
                'sect_name',
                'fk_role',
                'fk_entite',
                'chk_alert_email',
                'chk_suivi',
                'date_naissance',
                'date_embauche',
                'chk_active'
            ];

            foreach ($nonEncryptedFields as $field) {
                if (isset($data[$field])) {
                    $updateFields[] = "$field = :$field";
                    $params[$field] = is_string($data[$field]) ? trim($data[$field]) : $data[$field];
                }
            }

            // Mise à jour du mot de passe si fourni
            if (isset($data['password']) && !empty($data['password'])) {
                // Validation du mot de passe selon NIST SP 800-63B
                $passwordValidation = PasswordSecurityService::validatePassword($data['password']);
                
                if (!$passwordValidation['valid']) {
                    Response::json([
                        'status' => 'error',
                        'message' => 'Mot de passe invalide',
                        'errors' => $passwordValidation['errors'],
                        'warnings' => $passwordValidation['warnings']
                    ], 400);
                    return;
                }
                
                // Si le mot de passe a des avertissements mais est valide, les logger
                if (!empty($passwordValidation['warnings'])) {
                    LogService::log('Mot de passe avec avertissements accepté', [
                        'level' => 'warning',
                        'user_id' => $id,
                        'warnings' => $passwordValidation['warnings']
                    ]);
                }
                
                $updateFields[] = "user_pass_hash = :password";
                $params['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
            }

            // Ajout des champs de mise à jour
            $updateFields[] = "updated_at = NOW()";
            $updateFields[] = "fk_user_modif = :modifier_id";
            $params['modifier_id'] = $currentUserId;

            if (!empty($updateFields)) {
                $sql = 'UPDATE users SET ' . implode(', ', $updateFields) . ' WHERE id = :id';
                $stmt = $this->db->prepare($sql);
                $stmt->execute($params);

                if ($stmt->rowCount() === 0) {
                    Response::json([
                        'status' => 'warning',
                        'message' => 'Aucune modification effectuée'
                    ]);
                    return;
                }

                // Log de mise à jour utilisateur
                $changes = [];
                $encryptedFields = ['name', 'email', 'phone', 'mobile', 'username', 'password'];

                foreach ($data as $key => $value) {
                    if (in_array($key, $encryptedFields)) {
                        // Champs sensibles : booléen uniquement
                        $changes['encrypted_' . $key] = true;
                    } else {
                        // Champs non sensibles : valeur
                        $changes[$key] = ['new' => $value];
                    }
                }

                if (!empty($changes)) {
                    EventLogService::logUserUpdated((int)$id, $changes);
                }

                Response::json([
                    'status' => 'success',
                    'message' => 'Utilisateur mis à jour avec succès'
                ]);
            } else {
                Response::json([
                    'status' => 'warning',
                    'message' => 'Aucune donnée valide à mettre à jour'
                ]);
            }
        } catch (PDOException $e) {
            LogService::log('Erreur lors de la mise à jour d\'un utilisateur GeoSector', [
                'level' => 'error',
                'error' => $e->getMessage(),
                'userId' => $id
            ]);
            Response::json([
                'status' => 'error',
                'message' => 'Erreur serveur'
            ], 500);
        }
    }

    public function deleteUser(string $id): void {
        Session::requireAuth();

        $currentUserId = Session::getUserId();

        // Récupérer les infos de l'utilisateur courant
        $stmt = $this->db->prepare('SELECT fk_role, fk_entite FROM users WHERE id = ?');
        $stmt->execute([$currentUserId]);
        $currentUser = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$currentUser) {
            Response::json([
                'status' => 'error',
                'message' => 'Utilisateur courant non trouvé'
            ], 403);
            return;
        }

        $userRole = (int)$currentUser['fk_role'];
        $userEntite = $currentUser['fk_entite'];

        // Empêcher la suppression de son propre compte
        if ($currentUserId == $id) {
            Response::json([
                'status' => 'error',
                'message' => 'Vous ne pouvez pas supprimer votre propre compte'
            ], 400);
            return;
        }

        // Récupérer l'utilisateur cible
        $stmt2 = $this->db->prepare('SELECT fk_entite FROM users WHERE id = ?');
        $stmt2->execute([$id]);
        $userToDelete = $stmt2->fetch(PDO::FETCH_ASSOC);

        if (!$userToDelete) {
            Response::json([
                'status' => 'error',
                'message' => 'Utilisateur cible non trouvé'
            ], 404);
            return;
        }

        // Contrôle des droits
        if ($userRole === 1) {
            Response::json([
                'status' => 'error',
                'message' => "Vous n'avez pas le droit de supprimer un utilisateur"
            ], 403);
            return;
        } elseif ($userRole === 2) {
            if ($userEntite != $userToDelete['fk_entite']) {
                Response::json([
                    'status' => 'error',
                    'message' => "Vous n'avez pas le droit de supprimer un utilisateur d'une autre amicale"
                ], 403);
                return;
            }
        }
        // fk_role > 2 : tout est permis (hors auto-suppression)

        // ——— Gestion du transfert éventuel ———
        $transferTo = isset($_GET['transfer_to']) ? trim($_GET['transfer_to']) : null;

        if ($transferTo) {
            try {
                // Transférer les passages opération par opération
                // (car fk_user dans ope_pass pointe vers ope_users.id, pas users.id)

                // Récupérer toutes les opérations où l'utilisateur à supprimer a des entrées dans ope_users
                $stmtOps = $this->db->prepare('
                    SELECT DISTINCT fk_operation
                    FROM ope_users
                    WHERE fk_user = ?
                ');
                $stmtOps->execute([$id]);
                $operations = $stmtOps->fetchAll(PDO::FETCH_COLUMN);

                $totalTransferred = 0;

                foreach ($operations as $operationId) {
                    // Trouver ope_users.id de l'utilisateur à supprimer dans cette opération
                    $stmtOldOpeUser = $this->db->prepare('
                        SELECT id FROM ope_users
                        WHERE fk_user = ? AND fk_operation = ?
                    ');
                    $stmtOldOpeUser->execute([$id, $operationId]);
                    $oldOpeUserId = $stmtOldOpeUser->fetchColumn();

                    if (!$oldOpeUserId) {
                        continue;
                    }

                    // Trouver ope_users.id de l'utilisateur de destination dans cette opération
                    $stmtNewOpeUser = $this->db->prepare('
                        SELECT id FROM ope_users
                        WHERE fk_user = ? AND fk_operation = ?
                    ');
                    $stmtNewOpeUser->execute([$transferTo, $operationId]);
                    $newOpeUserId = $stmtNewOpeUser->fetchColumn();

                    if (!$newOpeUserId) {
                        LogService::log('Impossible de transférer passages - utilisateur destination absent', [
                            'level' => 'warning',
                            'operation_id' => $operationId,
                            'from_user' => $id,
                            'to_user' => $transferTo
                        ]);
                        continue;
                    }

                    // Transférer les passages
                    $stmtTransfer = $this->db->prepare('
                        UPDATE ope_pass
                        SET fk_user = :new_ope_user_id
                        WHERE fk_user = :old_ope_user_id AND fk_operation = :operation_id
                    ');
                    $stmtTransfer->execute([
                        'new_ope_user_id' => $newOpeUserId,
                        'old_ope_user_id' => $oldOpeUserId,
                        'operation_id' => $operationId
                    ]);

                    $totalTransferred += $stmtTransfer->rowCount();
                }

                LogService::log('Passages transférés avant suppression utilisateur', [
                    'level' => 'info',
                    'from_user' => $id,
                    'to_user' => $transferTo,
                    'operations_count' => count($operations),
                    'passages_transferred' => $totalTransferred
                ]);
            } catch (PDOException $e) {
                Response::json([
                    'status' => 'error',
                    'message' => 'Erreur lors du transfert des passages',
                    'error' => $e->getMessage()
                ], 500);
                return;
            }
        }

        // —— Suppression réelle de l'utilisateur ——
        try {
            // Supprimer les enregistrements dépendants dans ope_users
            // (CASCADE supprime automatiquement ope_users_sectors et ope_pass)
            $stmtOpeUsers = $this->db->prepare('DELETE FROM ope_users WHERE fk_user = ?');
            $stmtOpeUsers->execute([$id]);

            $stmt = $this->db->prepare('DELETE FROM users WHERE id = ?');
            $stmt->execute([$id]);

            if ($stmt->rowCount() === 0) {
                Response::json([
                    'status' => 'error',
                    'message' => 'Utilisateur non trouvé ou déjà supprimé'
                ], 404);
                return;
            }

            // Log de suppression utilisateur (suppression physique = false pour soft_delete)
            EventLogService::logUserDeleted((int)$id, false);

            Response::json([
                'status' => 'success',
                'message' => 'Utilisateur supprimé avec succès'
            ]);
        } catch (PDOException $e) {
            LogService::log('Erreur lors de la suppression d\'un utilisateur GeoSector', [
                'level' => 'error',
                'error' => $e->getMessage(),
                'userId' => $id
            ]);
            Response::json([
                'status' => 'error',
                'message' => 'Erreur serveur'
            ], 500);
        }
    }

    public function resetPassword(string $id): void {
        Session::requireAuth();

        $currentUserId = Session::getUserId();

        // Récupérer les infos de l'utilisateur courant
        $stmt = $this->db->prepare('SELECT fk_role, fk_entite, chk_active FROM users WHERE id = ?');
        $stmt->execute([$currentUserId]);
        $currentUser = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$currentUser) {
            Response::json([
                'status' => 'error',
                'message' => 'Utilisateur courant non trouvé'
            ], 403);
            return;
        }

        // Vérifier que l'utilisateur courant est actif
        if ($currentUser['chk_active'] != 1) {
            Response::json([
                'status' => 'error',
                'message' => 'Votre compte n\'est pas actif'
            ], 403);
            return;
        }

        $userRole = (int)$currentUser['fk_role'];
        $userEntite = $currentUser['fk_entite'];

        // Récupérer l'utilisateur cible
        $stmt = $this->db->prepare('
            SELECT id, encrypted_email, encrypted_name, fk_entite, chk_active 
            FROM users 
            WHERE id = ?
        ');
        $stmt->execute([$id]);
        $targetUser = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$targetUser) {
            Response::json([
                'status' => 'error',
                'message' => 'Utilisateur non trouvé'
            ], 404);
            return;
        }

        // Vérifier que l'utilisateur est actif
        if ($targetUser['chk_active'] != 1) {
            Response::json([
                'status' => 'error',
                'message' => 'L\'utilisateur n\'est pas actif'
            ], 400);
            return;
        }

        // Contrôle des droits selon le rôle
        if ($userRole === 1) {
            // Role 1 : peut uniquement réinitialiser son propre mot de passe
            if ($currentUserId != $id) {
                Response::json([
                    'status' => 'error',
                    'message' => 'Vous ne pouvez réinitialiser que votre propre mot de passe'
                ], 403);
                return;
            }
        } elseif ($userRole === 2) {
            // Role 2 : peut réinitialiser les mots de passe de sa propre entité
            if ($userEntite != $targetUser['fk_entite']) {
                Response::json([
                    'status' => 'error',
                    'message' => 'Vous ne pouvez réinitialiser que les mots de passe des utilisateurs de votre entité'
                ], 403);
                return;
            }
        }
        // Role > 2 : peut tout faire

        try {
            // Déchiffrement des données
            $email = ApiService::decryptSearchableData($targetUser['encrypted_email']);
            $name = ApiService::decryptData($targetUser['encrypted_name']);

            // Génération d'un nouveau mot de passe sécurisé
            $newPassword = ApiService::generateSecurePassword();
            $passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);

            // Mise à jour du mot de passe en base de données
            $updateStmt = $this->db->prepare('
                UPDATE users 
                SET user_pass_hash = :password,
                    updated_at = NOW(),
                    fk_user_modif = :modifier_id
                WHERE id = :id
            ');
            $updateStmt->execute([
                'password' => $passwordHash,
                'modifier_id' => $currentUserId,
                'id' => $id
            ]);

            // Envoi de l'email avec le nouveau mot de passe
            ApiService::sendEmail($email, $name, 'password_reset', ['password' => $newPassword]);

            LogService::log('Mot de passe réinitialisé', [
                'level' => 'info',
                'resetBy' => $currentUserId,
                'userId' => $id,
                'email' => $email
            ]);

            Response::json([
                'status' => 'success',
                'message' => 'Mot de passe réinitialisé avec succès. Un email a été envoyé à l\'utilisateur.'
            ]);

        } catch (PDOException $e) {
            LogService::log('Erreur lors de la réinitialisation du mot de passe', [
                'level' => 'error',
                'error' => $e->getMessage(),
                'userId' => $id
            ]);
            Response::json([
                'status' => 'error',
                'message' => 'Erreur serveur'
            ], 500);
        }
    }

    public function checkUsername(): void {
        Session::requireAuth();
        
        try {
            $data = Request::getJson();
            
            // Log de la requête
            LogService::log('Vérification de disponibilité username', [
                'level' => 'debug',
                'checkedBy' => Session::getUserId(),
                'has_username' => isset($data['username'])
            ]);
            
            // Validation de la présence du username
            if (!isset($data['username']) || empty(trim($data['username']))) {
                LogService::log('Erreur vérification username : Username manquant', [
                    'level' => 'warning',
                    'checkedBy' => Session::getUserId()
                ]);
                Response::json([
                    'status' => 'error',
                    'message' => 'Identifiant requis',
                    'field' => 'username'
                ], 400);
                return;
            }
            
            // Trim du username mais on garde la casse originale
            $username = trim($data['username']);
            
            // Validation ultra-souple : seulement la longueur en caractères UTF-8
            $usernameLength = mb_strlen($username, 'UTF-8');
            
            if ($usernameLength < 8) {
                LogService::log('Erreur vérification username : Username trop court', [
                    'level' => 'warning',
                    'checkedBy' => Session::getUserId(),
                    'username_length' => $usernameLength
                ]);
                Response::json([
                    'status' => 'error',
                    'message' => 'Identifiant trop court',
                    'field' => 'username',
                    'details' => 'Minimum 8 caractères',
                    'available' => false
                ], 400);
                return;
            }
            
            if ($usernameLength > 30) {
                LogService::log('Erreur vérification username : Username trop long', [
                    'level' => 'warning',
                    'checkedBy' => Session::getUserId(),
                    'username_length' => $usernameLength
                ]);
                Response::json([
                    'status' => 'error',
                    'message' => 'Identifiant trop long',
                    'field' => 'username',
                    'details' => 'Maximum 30 caractères',
                    'available' => false
                ], 400);
                return;
            }
            
            // Chiffrement du username pour la recherche
            $encryptedUsername = ApiService::encryptSearchableData($username);
            
            // Vérification de l'existence dans la base
            $stmt = $this->db->prepare('
                SELECT id, encrypted_name, fk_entite 
                FROM users 
                WHERE encrypted_user_name = ?
                LIMIT 1
            ');
            $stmt->execute([$encryptedUsername]);
            $existingUser = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if ($existingUser) {
                // Username déjà pris - générer des suggestions
                $baseName = substr($username, 0, -2); // Enlever les 2 derniers caractères
                $suggestions = [];
                
                // Génération de 3 suggestions
                $suggestions[] = $username . '_' . rand(10, 99);
                $suggestions[] = $baseName . rand(100, 999);
                
                // Suggestion avec l'année courante
                $year = date('y');
                $suggestions[] = $username . $year;
                
                // Vérifier que les suggestions sont aussi disponibles
                $availableSuggestions = [];
                foreach ($suggestions as $suggestion) {
                    $encryptedSuggestion = ApiService::encryptSearchableData($suggestion);
                    $checkStmt = $this->db->prepare('SELECT id FROM users WHERE encrypted_user_name = ?');
                    $checkStmt->execute([$encryptedSuggestion]);
                    if (!$checkStmt->fetch()) {
                        $availableSuggestions[] = $suggestion;
                    }
                }
                
                // Si aucune suggestion n'est disponible, en générer d'autres
                if (empty($availableSuggestions)) {
                    for ($i = 0; $i < 3; $i++) {
                        $randomSuffix = rand(1000, 9999);
                        $availableSuggestions[] = $baseName . $randomSuffix;
                    }
                }
                
                Response::json([
                    'status' => 'success',
                    'available' => false,
                    'message' => 'Ce nom d\'utilisateur est déjà utilisé',
                    'suggestions' => array_slice($availableSuggestions, 0, 3)
                ]);
            } else {
                // Username disponible
                Response::json([
                    'status' => 'success',
                    'available' => true,
                    'message' => 'Nom d\'utilisateur disponible',
                    'username' => $username
                ]);
            }
            
        } catch (PDOException $e) {
            LogService::log('Erreur lors de la vérification du username', [
                'level' => 'error',
                'error' => $e->getMessage()
            ]);
            Response::json([
                'status' => 'error',
                'message' => 'Erreur serveur lors de la vérification'
            ], 500);
        }
    }

    /**
     * Enregistre les informations du device de l'utilisateur
     * POST /api/users/device-info
     */
    public function saveDeviceInfo(): void {
        Session::requireAuth();

        $userId = Session::getUserId();
        if (!$userId) {
            LogService::log('Device info error: Invalid session', [
                'level' => 'error',
                'session_id' => session_id()
            ]);
            Response::json([
                'status' => 'error',
                'message' => 'Session invalide'
            ], 401);
            return;
        }

        // Récupération des données du payload
        $data = Request::getJson();

        // Validation des données requises
        if (empty($data['platform'])) {
            LogService::log('Device info error: Missing platform', [
                'level' => 'error',
                'user_id' => $userId,
                'data' => $data
            ]);
            Response::json([
                'status' => 'error',
                'message' => 'Platform requis',
                'field' => 'platform'
            ], 400);
            return;
        }

        // Validation des IPs (IPv4 uniquement)
        // Pour la plateforme Web, device_ip_local contient "Web Platform" → on le traite comme NULL
        $deviceIpLocal = $data['device_ip_local'] ?? null;
        $deviceIpPublic = $data['device_ip_public'] ?? null;

        if ($deviceIpLocal && !filter_var($deviceIpLocal, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            LogService::log('Device IP local invalide, ignorée', [
                'level' => 'debug',
                'user_id' => $userId,
                'device_ip_local' => $deviceIpLocal
            ]);
            $deviceIpLocal = null; // Ignorer si ce n'est pas une IPv4 valide
        }

        if ($deviceIpPublic && !filter_var($deviceIpPublic, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            LogService::log('Device IP public invalide, ignorée', [
                'level' => 'debug',
                'user_id' => $userId,
                'device_ip_public' => $deviceIpPublic
            ]);
            $deviceIpPublic = null; // Ignorer si ce n'est pas une IPv4 valide
        }

        // Validation du niveau de batterie (0-100)
        $batteryLevel = isset($data['battery_level']) ? intval($data['battery_level']) : null;
        if ($batteryLevel !== null && ($batteryLevel < 0 || $batteryLevel > 100)) {
            $batteryLevel = null;
        }

        // Conversion des booléens
        $nfcCapable = isset($data['device_nfc_capable']) ? ($data['device_nfc_capable'] ? 1 : 0) : null;
        $tapToPay = isset($data['device_supports_tap_to_pay']) ? ($data['device_supports_tap_to_pay'] ? 1 : 0) : null;
        $batteryCharging = isset($data['battery_charging']) ? ($data['battery_charging'] ? 1 : 0) : null;

        // Conversion de la date de check
        $lastDeviceInfoCheck = null;
        if (!empty($data['last_device_info_check'])) {
            try {
                $date = new \DateTime($data['last_device_info_check']);
                $lastDeviceInfoCheck = $date->format('Y-m-d H:i:s');
            } catch (\Exception $e) {
                // Ignorer si le format de date est invalide
            }
        }

        try {
            $this->db->beginTransaction();

            // Pour les plateformes Web sans device_identifier, générer un identifiant unique basé sur user_id + platform
            $deviceIdentifier = $data['device_identifier'] ?? null;

            // Si device_identifier est vide ou NULL pour la plateforme Web, générer un identifiant
            if (empty($deviceIdentifier)) {
                $platform = $data['platform'] ?? 'unknown';

                if ($platform === 'Web') {
                    // Un seul device Web par utilisateur
                    $deviceIdentifier = 'web_' . $userId . '_' . md5($userId . '_web');
                } else {
                    // Pour les autres plateformes sans identifier, générer un identifiant aléatoire
                    $deviceIdentifier = strtolower($platform) . '_' . $userId . '_' . uniqid();
                }

                LogService::log('Device identifier généré automatiquement', [
                    'level' => 'debug',
                    'user_id' => $userId,
                    'platform' => $platform,
                    'device_identifier' => $deviceIdentifier
                ]);
            }

            // Requête INSERT ... ON DUPLICATE KEY UPDATE
            $sql = "INSERT INTO user_devices (
                fk_user, platform, device_model, device_name, device_manufacturer,
                device_identifier, device_ip_local, device_ip_public, device_wifi_name,
                device_wifi_bssid, ios_version, device_nfc_capable, device_supports_tap_to_pay,
                battery_level, battery_charging, battery_state, app_version, app_build,
                last_device_info_check
            ) VALUES (
                :fk_user, :platform, :device_model, :device_name, :device_manufacturer,
                :device_identifier, :device_ip_local, :device_ip_public, :device_wifi_name,
                :device_wifi_bssid, :ios_version, :device_nfc_capable, :device_supports_tap_to_pay,
                :battery_level, :battery_charging, :battery_state, :app_version, :app_build,
                :last_device_info_check
            ) ON DUPLICATE KEY UPDATE
                platform = VALUES(platform),
                device_model = VALUES(device_model),
                device_name = VALUES(device_name),
                device_manufacturer = VALUES(device_manufacturer),
                device_ip_local = VALUES(device_ip_local),
                device_ip_public = VALUES(device_ip_public),
                device_wifi_name = VALUES(device_wifi_name),
                device_wifi_bssid = VALUES(device_wifi_bssid),
                ios_version = VALUES(ios_version),
                device_nfc_capable = VALUES(device_nfc_capable),
                device_supports_tap_to_pay = VALUES(device_supports_tap_to_pay),
                battery_level = VALUES(battery_level),
                battery_charging = VALUES(battery_charging),
                battery_state = VALUES(battery_state),
                app_version = VALUES(app_version),
                app_build = VALUES(app_build),
                last_device_info_check = VALUES(last_device_info_check),
                updated_at = CURRENT_TIMESTAMP";

            $stmt = $this->db->prepare($sql);

            $params = [
                ':fk_user' => $userId,
                ':platform' => $data['platform'] ?? null,
                ':device_model' => $data['device_model'] ?? null,
                ':device_name' => $data['device_name'] ?? null,
                ':device_manufacturer' => $data['device_manufacturer'] ?? null,
                ':device_identifier' => $deviceIdentifier,
                ':device_ip_local' => $deviceIpLocal,
                ':device_ip_public' => $deviceIpPublic,
                ':device_wifi_name' => $data['device_wifi_name'] ?? null,
                ':device_wifi_bssid' => $data['device_wifi_bssid'] ?? null,
                ':ios_version' => $data['ios_version'] ?? null,
                ':device_nfc_capable' => $nfcCapable,
                ':device_supports_tap_to_pay' => $tapToPay,
                ':battery_level' => $batteryLevel,
                ':battery_charging' => $batteryCharging,
                ':battery_state' => $data['battery_state'] ?? null,
                ':app_version' => $data['app_version'] ?? null,
                ':app_build' => $data['app_build'] ?? null,
                ':last_device_info_check' => $lastDeviceInfoCheck
            ];

            $stmt->execute($params);

            $this->db->commit();

            LogService::log('Device info enregistrées avec succès', [
                'level' => 'info',
                'user_id' => $userId,
                'platform' => $data['platform'],
                'device_identifier' => $deviceIdentifier
            ]);

            Response::json([
                'status' => 'success',
                'message' => 'Informations du device enregistrées'
            ], 200);

        } catch (\PDOException $e) {
            if ($this->db->inTransaction()) {
                $this->db->rollBack();
            }

            LogService::log('Error saving device info', [
                'level' => 'error',
                'user_id' => $userId,
                'error' => $e->getMessage(),
                'code' => $e->getCode(),
                'platform' => $data['platform'] ?? 'unknown'
            ]);

            Response::json([
                'status' => 'error',
                'message' => 'Erreur lors de l\'enregistrement des informations du device',
                'details' => $e->getMessage()
            ], 500);
        }
    }

    // Méthodes auxiliaires
    private function validateUpdateData(array $data): ?string {
        // Validation de l'email
        if (isset($data['email'])) {
            if (!filter_var(trim($data['email']), FILTER_VALIDATE_EMAIL)) {
                return 'Format d\'email invalide';
            }
        }

        // Validation du nom
        if (isset($data['name']) && strlen(trim($data['name'])) < 2) {
            return 'Le nom doit contenir au moins 2 caractères';
        }

        // Validation du téléphone
        if (isset($data['phone']) && !empty($data['phone'])) {
            if (!preg_match('/^[0-9+\s()-]{6,20}$/', trim($data['phone']))) {
                return 'Format de téléphone invalide';
            }
        }

        // Validation du mobile
        if (isset($data['mobile']) && !empty($data['mobile'])) {
            if (!preg_match('/^[0-9+\s()-]{6,20}$/', trim($data['mobile']))) {
                return 'Format de mobile invalide';
            }
        }

        return null;
    }
}
