# Cheatsheet MongoDB - Guide Ultra-Détaillé pour Grands Débutants


[OK] CONCEPTS FONDAMENTAUX (EXPLICATIONS TRÈS DÉTAILLÉES)

# === QU'EST-CE QUE MONGODB ? ===

# Imagine que tu veux stocker des données dans une application
# Solution classique (SQL - MySQL, PostgreSQL):
# - Tables avec colonnes fixes
# - Chaque ligne doit avoir EXACTEMENT les mêmes colonnes
# - Difficile de changer la structure
# - Exemple: Table "users" avec colonnes: id, name, email, age

# Problème avec SQL:
# Si tu veux ajouter "hobbies" pour CERTAINS utilisateurs seulement?
# - Créer une colonne "hobbies" pour TOUS (gaspillage si vide)
# - Créer une table séparée (complexe, nécessite JOIN)

# MONGODB = Base de données NoSQL qui stocke des DOCUMENTS
# = Au lieu de lignes dans une table, tu as des DOCUMENTS JSON
# = Chaque document peut avoir des champs différents!
# = Flexible, rapide, facile à utiliser

# MongoDB est une base de données NoSQL orientée documents
# NoSQL = "Not Only SQL" = Pas seulement SQL
# Orientée documents = Stocke des documents JSON/BSON

# Analogie simple:
# SQL = Classeur avec des fiches toutes identiques
# MongoDB = Boîte où tu mets n'importe quel type de papier

# === POURQUOI UTILISER MONGODB? ===

# 1. FLEXIBILITÉ DU SCHÉMA
# SQL: Structure rigide, tous les enregistrements identiques
# MongoDB: Chaque document peut être différent

# Exemple SQL (rigide):
# Table "users"
# | id | name    | email           | age |
# |----|---------|-----------------|-----|
# | 1  | Alice   | alice@mail.com  | 25  |
# | 2  | Bob     | bob@mail.com    | 30  |
# Problème: Si Bob a un "nickname" mais pas Alice? Difficile!

# Exemple MongoDB (flexible):
# Collection "users"
# Document 1:
# {
#   "_id": 1,
#   "name": "Alice",
#   "email": "alice@mail.com",
#   "age": 25
# }
#
# Document 2:
# {
#   "_id": 2,
#   "name": "Bob",
#   "email": "bob@mail.com",
#   "age": 30,
#   "nickname": "Bobby",
#   "hobbies": ["coding", "gaming"]
# }
# = Chaque document peut avoir des champs différents!

# 2. FACILITÉ DE DÉVELOPPEMENT
# Pas besoin de créer des schémas complexes avant de commencer
# Les données JSON/Python dict s'insèrent directement
# Pas de migration de schéma compliquée

# 3. PERFORMANCE POUR CERTAINS CAS
# Très rapide pour lire/écrire des documents complets
# Pas de JOIN coûteux (tout dans un document)
# Bon pour les applications web modernes

# 4. SCALABILITÉ HORIZONTALE
# Facile d'ajouter plus de serveurs (sharding)
# Distribue les données automatiquement

# === QUAND UTILISER MONGODB? ===

# [OK] BON POUR:
# - Applications web/mobile avec données flexibles
# - Prototypes rapides (pas besoin de définir schéma d'avance)
# - Données hiérarchiques/imbriquées (JSON naturellement)
# - Logs et événements (structure variable)
# - Catalogues de produits (chaque produit différent)
# - Systèmes de gestion de contenu (CMS)
# - Applications temps réel (chat, notifications)

# [X] PAS BON POUR:
# - Transactions bancaires complexes (SQL mieux)
# - Données très structurées et stables
# - Beaucoup de relations complexes entre données
# - Nécessite des JOIN complexes fréquents
# - Conformité stricte ACID sur plusieurs tables

# === VOCABULAIRE MONGODB (TRÈS IMPORTANT!) ===

# DATABASE (Base de données)
# = Conteneur pour plusieurs collections
# = Comme un dossier qui contient plusieurs fichiers
# Exemple: "myapp_db" peut contenir "users", "posts", "comments"

# COLLECTION (Collection)
# = Équivalent d'une TABLE en SQL
# = Groupe de documents similaires
# = Pas de schéma fixe!
# Exemple: Collection "users" contient tous les utilisateurs

# DOCUMENT (Document)
# = Équivalent d'une LIGNE en SQL
# = Un enregistrement individuel
# = Format JSON/BSON (Binary JSON)
# Exemple: Un document utilisateur
# {
#   "_id": ObjectId("507f1f77bcf86cd799439011"),
#   "name": "Alice",
#   "email": "alice@mail.com"
# }

# FIELD (Champ)
# = Équivalent d'une COLONNE en SQL
# = Paire clé-valeur dans un document
# Exemple: "name": "Alice" -> "name" est le champ

# _id (Identifiant unique)
# = Clé primaire automatique
# = Unique dans une collection
# = Généré automatiquement si non fourni
# = Type: ObjectId (12 bytes, timestamp + random)

# BSON (Binary JSON)
# = Format de stockage interne de MongoDB
# = JSON + types supplémentaires (Date, Binary, etc)
# = Plus efficace que JSON texte
# Tu écris du JSON, MongoDB stocke en BSON

# === COMPARAISON SQL <-> MONGODB ===

# SQL                    MongoDB
# ────────────────────   ─────────────────────
# Database               Database
# Table                  Collection
# Row                    Document
# Column                 Field
# Primary Key            _id (automatique)
# JOIN                   Embedded documents ou $lookup
# Schema fixe            Schema flexible
# SQL query              JavaScript/JSON query

# === COMMENT ÇA MARCHE? (FLUX COMPLET) ===

# 1. Installation de MongoDB
#    - Serveur MongoDB tourne sur port 27017 (par défaut)
#    - Le serveur stocke les données sur disque
#    - Tu te connectes avec un client (Python, mongo shell, etc)

# 2. Connexion depuis Python:
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/")
# Client = connexion au serveur MongoDB

# 3. Sélectionner/créer une base de données:
db = client["myapp_db"]
# Si "myapp_db" n'existe pas, elle sera créée au premier document

# 4. Sélectionner/créer une collection:
collection = db["users"]
# Si "users" n'existe pas, elle sera créée au premier document

# 5. Insérer un document:
user = {"name": "Alice", "email": "alice@mail.com", "age": 25}
result = collection.insert_one(user)
# MongoDB ajoute automatiquement un _id

# 6. Lire des documents:
all_users = collection.find()
# Retourne un curseur (itérable) de tous les documents

# 7. Mettre à jour:
collection.update_one(
    {"name": "Alice"},           # Filtre: quel document?
    {"$set": {"age": 26}}        # Mise à jour: quoi changer?
)

# 8. Supprimer:
collection.delete_one({"name": "Alice"})

# === TYPES DE DONNÉES BSON ===

# MongoDB supporte plus de types que JSON standard:

# String (Chaîne de caractères)
{"name": "Alice"}

# Integer (Nombre entier)
{"age": 25}

# Double (Nombre décimal)
{"price": 19.99}

# Boolean (Booléen)
{"is_active": True}

# Array (Tableau/Liste)
{"hobbies": ["reading", "coding", "gaming"]}

# Object (Objet imbriqué)
{
    "address": {
        "street": "123 Main St",
        "city": "Paris"
    }
}

# Date (Date et heure)
from datetime import datetime
{"created_at": datetime.now()}

# Null (Valeur nulle)
{"middle_name": None}

# ObjectId (Identifiant unique MongoDB)
from bson import ObjectId
{"_id": ObjectId("507f1f77bcf86cd799439011")}

# Binary (Données binaires)
{"profile_picture": Binary(b"...")}

# Regex (Expression régulière)
{"pattern": {"$regex": "^A"}}

# === STRUCTURE D'UN DOCUMENT TYPIQUE ===

# Document utilisateur complet:
{
    "_id": ObjectId("507f1f77bcf86cd799439011"),  # Généré automatiquement
    "username": "alice123",                        # String
    "email": "alice@mail.com",                     # String
    "age": 25,                                     # Integer
    "is_active": True,                             # Boolean
    "created_at": ISODate("2024-01-15T10:30:00Z"), # Date
    "hobbies": ["coding", "reading", "gaming"],    # Array
    "address": {                                   # Embedded document
        "street": "123 Main St",
        "city": "Paris",
        "country": "France"
    },
    "friends": [                                   # Array of ObjectIds
        ObjectId("507f1f77bcf86cd799439012"),
        ObjectId("507f1f77bcf86cd799439013")
    ],
    "metadata": {                                  # Nested object
        "last_login": ISODate("2024-01-15T18:45:00Z"),
        "login_count": 42
    }
}


[OK] INSTALLATION SUPER DÉTAILLÉE

# === MÉTHODE 1: INSTALLATION LOCALE (SERVEUR MONGODB) ===

# MongoDB se compose de 2 parties:
# 1. Serveur MongoDB (mongod) = Le programme qui stocke les données
# 2. Client Python (pymongo) = La bibliothèque pour se connecter depuis Python

# === ÉTAPE 1A: INSTALLER LE SERVEUR MONGODB ===

# ========== macOS (avec Homebrew) ==========

# Si tu n'as pas Homebrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Installer MongoDB Community Edition:
brew tap mongodb/brew
brew install mongodb-community@7.0

# Démarrer MongoDB:
brew services start mongodb-community@7.0

# Vérifier que MongoDB tourne:
brew services list
# Doit afficher: mongodb-community@7.0 started

# MongoDB écoute sur: localhost:27017

# Arrêter MongoDB:
brew services stop mongodb-community@7.0


# ========== Linux (Ubuntu/Debian) ==========

# Importer la clé GPG MongoDB:
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
   sudo gpg --dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg

# Ajouter le dépôt MongoDB:
echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | \
   sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list

# Mettre à jour les paquets:
sudo apt-get update

# Installer MongoDB:
sudo apt-get install -y mongodb-org

# Démarrer MongoDB:
sudo systemctl start mongod

# Activer au démarrage:
sudo systemctl enable mongod

# Vérifier le statut:
sudo systemctl status mongod
# Doit afficher: active (running)

# MongoDB écoute sur: localhost:27017

# Arrêter MongoDB:
sudo systemctl stop mongod


# ========== Windows ==========

# 1. Télécharger MongoDB:
# Ouvre un navigateur et va sur:
# https://www.mongodb.com/try/download/community

# 2. Choisis:
# - Version: 7.0 (ou dernière)
# - Platform: Windows
# - Package: msi

# 3. Télécharge le fichier .msi

# 4. Double-clique sur le fichier téléchargé

# 5. Installation:
# - Clique "Next" plusieurs fois
# - Choisis "Complete" installation
# - Coche "Install MongoDB as a Service"
# - Laisse les paramètres par défaut
# - Clique "Install"

# 6. MongoDB démarre automatiquement

# 7. Vérifier l'installation:
# Ouvre PowerShell et tape:
mongod --version
# Doit afficher: db version v7.0.x

# MongoDB écoute sur: localhost:27017


# === ÉTAPE 1B: TESTER LE SERVEUR MONGODB ===

# Ouvre un terminal et connecte-toi au serveur avec mongo shell:

# MongoDB >= 6.0 (mongosh):
mongosh

# MongoDB < 6.0 (mongo):
mongo

# Tu dois voir:
# Connecting to: mongodb://127.0.0.1:27017
# MongoDB server version: 7.0.x

# Essayer quelques commandes:
show dbs                    # Affiche les bases de données
use test                    # Sélectionne/crée la base "test"
db.myCollection.insertOne({name: "Alice"})  # Insère un document
db.myCollection.find()      # Affiche les documents

# Pour quitter:
exit


# === ÉTAPE 2: INSTALLER LE CLIENT PYTHON (pymongo) ===

# pymongo = Bibliothèque Python pour se connecter à MongoDB

# IMPORTANT: Toujours utiliser un environnement virtuel!

# Créer un environnement virtuel:

# macOS/Linux:
python3 -m venv venv
source venv/bin/activate

# Windows:
python -m venv venv
venv\Scripts\activate

# Tu dois voir: (venv) $ ou (venv) C:\...

# Installer pymongo:
pip install pymongo

# Vérifier l'installation:
pip list | grep pymongo
# Doit afficher: pymongo X.X.X

# Version avec extras (recommandé pour production):
pip install "pymongo[srv]"
# [srv] = Support pour les connexions MongoDB Atlas (cloud)


# === MÉTHODE 2: UTILISER MONGODB ATLAS (CLOUD GRATUIT) ===

# MongoDB Atlas = MongoDB hébergé dans le cloud
# Avantages:
# - Pas besoin d'installer un serveur local
# - Gratuit jusqu'à 512 MB
# - Accessible de partout
# - Gestion automatique des sauvegardes

# === ÉTAPE 1: CRÉER UN COMPTE ATLAS ===

# 1. Ouvre un navigateur et va sur:
# https://www.mongodb.com/cloud/atlas

# 2. Clique sur "Try Free"

# 3. Remplis le formulaire:
# - Email
# - Mot de passe
# - Prénom/Nom

# 4. Vérifie ton email et confirme

# 5. Connecte-toi à MongoDB Atlas


# === ÉTAPE 2: CRÉER UN CLUSTER (BASE DE DONNÉES CLOUD) ===

# 1. Clique sur "Build a Database"

# 2. Choisis "Free" (M0 Sandbox)
# - 512 MB storage
# - Shared RAM
# - Gratuit pour toujours!

# 3. Choisis une région:
# - AWS, GCP ou Azure
# - Choisis la région la plus proche (Europe pour toi)

# 4. Nomme ton cluster (ex: "Cluster0")

# 5. Clique "Create Cluster"
# Attends 1-3 minutes pour la création


# === ÉTAPE 3: CRÉER UN UTILISATEUR ===

# 1. Clique sur "Database Access" (menu gauche)

# 2. Clique "Add New Database User"

# 3. Remplis:
# - Username: myuser
# - Password: mypassword123 (note-le bien!)
# - Database User Privileges: "Read and write to any database"

# 4. Clique "Add User"


# === ÉTAPE 4: AUTORISER TON IP ===

# 1. Clique sur "Network Access" (menu gauche)

# 2. Clique "Add IP Address"

# 3. Choisis:
# - "Allow Access from Anywhere" (pour développement)
# - Ou entre ton IP spécifique

# 4. Clique "Confirm"


# === ÉTAPE 5: OBTENIR LA CHAÎNE DE CONNEXION ===

# 1. Clique sur "Database" (menu gauche)

# 2. Clique "Connect" sur ton cluster

# 3. Choisis "Connect your application"

# 4. Choisis:
# - Driver: Python
# - Version: 3.12 or later

# 5. Copie la chaîne de connexion:
# mongodb+srv://myuser:<password>@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority

# 6. Remplace <password> par ton mot de passe réel:
# mongodb+srv://myuser:mypassword123@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority


# === ÉTAPE 6: SE CONNECTER DEPUIS PYTHON ===

from pymongo import MongoClient

# Chaîne de connexion Atlas:
uri = "mongodb+srv://myuser:mypassword123@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority"

# Créer le client:
client = MongoClient(uri)

# Tester la connexion:
try:
    client.admin.command('ping')
    print("[OK] Connexion réussie à MongoDB Atlas!")
except Exception as e:
    print(f"[X] Erreur de connexion: {e}")


# === COMPARAISON: LOCAL vs ATLAS ===

# LOCAL (mongod sur ton ordinateur)
# [OK] Gratuit
# [OK] Données sur ton disque
# [OK] Pas de connexion internet nécessaire
# [OK] Rapide (pas de latence réseau)
# [X] Doit installer et gérer MongoDB
# [X] Accessible uniquement depuis ton ordinateur
# [X] Pas de sauvegardes automatiques

# ATLAS (MongoDB Cloud)
# [OK] Gratuit (512 MB)
# [OK] Pas d'installation
# [OK] Accessible de partout
# [OK] Sauvegardes automatiques
# [OK] Mise à l'échelle facile
# [X] Nécessite une connexion internet
# [X] Latence réseau (plus lent que local)
# [X] Limites sur le tier gratuit


[OK] CONNEXION À MONGODB (EXPLICATIONS TRÈS DÉTAILLÉES)

# === CONNEXION LOCAL ===

from pymongo import MongoClient

# Méthode 1: Connexion simple (local)
client = MongoClient()
# Par défaut: mongodb://localhost:27017/

# Méthode 2: Spécifier l'hôte et le port
client = MongoClient("localhost", 27017)

# Méthode 3: URI complète
client = MongoClient("mongodb://localhost:27017/")

# Explications:
# - "localhost" = ton ordinateur
# - 27017 = port par défaut de MongoDB
# - client = objet de connexion à MongoDB

# Vérifier la connexion:
try:
    # Commande "ping" pour tester
    client.admin.command('ping')
    print("[OK] Connexion réussie!")
except Exception as e:
    print(f"[X] Erreur: {e}")


# === CONNEXION ATLAS (CLOUD) ===

from pymongo import MongoClient

# URI MongoDB Atlas:
uri = "mongodb+srv://username:password@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority"

# Créer le client:
client = MongoClient(uri)

# Explications de l'URI:
# mongodb+srv:// = Protocole sécurisé (SRV DNS)
# username:password = Identifiants de l'utilisateur DB
# @cluster0.xxxxx.mongodb.net = Adresse du cluster
# ?retryWrites=true = Réessaie automatiquement les écritures
# &w=majority = Attend confirmation de la majorité des serveurs

# Tester la connexion:
try:
    client.admin.command('ping')
    print("[OK] Connexion Atlas réussie!")
except Exception as e:
    print(f"[X] Erreur: {e}")


# === CONNEXION AVEC AUTHENTIFICATION ===

# Si MongoDB nécessite un nom d'utilisateur et mot de passe:

from pymongo import MongoClient

# Méthode 1: Dans l'URI
uri = "mongodb://myuser:mypassword@localhost:27017/"
client = MongoClient(uri)

# Méthode 2: Paramètres séparés
client = MongoClient(
    host="localhost",
    port=27017,
    username="myuser",
    password="mypassword",
    authSource="admin"  # Base de données d'authentification
)

# authSource:
# - "admin" = base par défaut pour les utilisateurs
# - Peut être une autre base si configuré différemment


# === CONNEXION AVEC OPTIONS AVANCÉES ===

from pymongo import MongoClient

client = MongoClient(
    host="localhost",
    port=27017,
    
    # Timeout de connexion (millisecondes)
    connectTimeoutMS=5000,  # 5 secondes
    
    # Timeout d'opération
    socketTimeoutMS=5000,
    
    # Taille du pool de connexions
    maxPoolSize=50,
    minPoolSize=10,
    
    # Nom de l'application (pour logs)
    appName="MyApp",
    
    # Niveau de concern d'écriture
    w="majority",  # Attend confirmation de la majorité
    
    # Journal des écritures
    journal=True,
    
    # Timeout pour l'écriture
    wtimeout=5000  # 5 secondes
)

# Explications:
# - connectTimeoutMS = Temps max pour établir la connexion
# - socketTimeoutMS = Temps max pour une opération
# - maxPoolSize = Nombre max de connexions simultanées
# - w="majority" = Sécurité: attend que la majorité des serveurs confirme
# - journal=True = Écrit dans le journal avant de confirmer


# === SÉLECTIONNER UNE BASE DE DONNÉES ===

# Méthode 1: Notation avec point
db = client.myapp_db
# Si "myapp_db" n'existe pas, elle sera créée au premier document

# Méthode 2: Notation avec crochets (recommandé si nom avec espace/tiret)
db = client["myapp_db"]
db = client["my-app-db"]  # Avec tiret
db = client["my app db"]   # Avec espace

# La base de données n'est créée physiquement qu'au premier document inséré

# Vérifier les bases de données existantes:
print(client.list_database_names())
# Affiche: ['admin', 'config', 'local', 'myapp_db']


# === SÉLECTIONNER UNE COLLECTION ===

# Méthode 1: Notation avec point
collection = db.users
# Si "users" n'existe pas, elle sera créée au premier document

# Méthode 2: Notation avec crochets (recommandé)
collection = db["users"]
collection = db["my-users"]  # Avec tiret

# La collection n'est créée physiquement qu'au premier document inséré

# Vérifier les collections existantes:
print(db.list_collection_names())
# Affiche: ['users', 'posts', 'comments']


# === EXEMPLE COMPLET DE CONNEXION ===

from pymongo import MongoClient
from pymongo.errors import ConnectionFailure

def connect_to_mongodb():
    """
    Fonction pour se connecter à MongoDB avec gestion d'erreurs
    """
    try:
        # Créer le client
        client = MongoClient(
            "mongodb://localhost:27017/",
            serverSelectionTimeoutMS=5000  # 5 sec timeout
        )
        
        # Tester la connexion
        client.admin.command('ping')
        print("[OK] Connexion réussie à MongoDB!")
        
        # Sélectionner la base de données
        db = client["myapp_db"]
        
        # Sélectionner la collection
        collection = db["users"]
        
        return client, db, collection
        
    except ConnectionFailure as e:
        print(f"[X] Impossible de se connecter à MongoDB: {e}")
        return None, None, None
    except Exception as e:
        print(f"[X] Erreur inattendue: {e}")
        return None, None, None

# Utilisation:
client, db, collection = connect_to_mongodb()

if client:
    # Faire des opérations...
    print(f"Base de données: {db.name}")
    print(f"Collection: {collection.name}")
    
    # Fermer la connexion quand terminé
    client.close()


# === FERMER LA CONNEXION ===

# TOUJOURS fermer la connexion quand terminé!

# Méthode 1: Explicite
client.close()

# Méthode 2: Context manager (recommandé)
with MongoClient("mongodb://localhost:27017/") as client:
    db = client["myapp_db"]
    collection = db["users"]
    # Faire des opérations...
# La connexion est automatiquement fermée ici


# === VÉRIFIER L'ÉTAT DE LA CONNEXION ===

from pymongo import MongoClient

client = MongoClient("mongodb://localhost:27017/")

# Obtenir des informations sur le serveur:
server_info = client.server_info()
print(f"Version MongoDB: {server_info['version']}")
print(f"Uptime: {server_info['uptimeMillis']} ms")

# Lister les bases de données:
databases = client.list_database_names()
print(f"Bases de données: {databases}")

# Statistiques de la base:
db = client["myapp_db"]
stats = db.command("dbstats")
print(f"Nombre de collections: {stats['collections']}")
print(f"Taille des données: {stats['dataSize']} bytes")


[OK] OPÉRATIONS CRUD DÉTAILLÉES

# CRUD = Create, Read, Update, Delete
# Les 4 opérations de base sur les données

# === CREATE (CRÉER DES DOCUMENTS) ===

from pymongo import MongoClient
from datetime import datetime

# Connexion
client = MongoClient("mongodb://localhost:27017/")
db = client["myapp_db"]
collection = db["users"]


# === INSÉRER UN SEUL DOCUMENT (insert_one) ===

# Créer un document (dictionnaire Python)
user = {
    "name": "Alice",
    "email": "alice@mail.com",
    "age": 25,
    "is_active": True,
    "created_at": datetime.now()
}

# Insérer le document
result = collection.insert_one(user)

# result.inserted_id = L'_id du document inséré
print(f"Document inséré avec _id: {result.inserted_id}")
# Affiche: Document inséré avec _id: 507f1f77bcf86cd799439011

# IMPORTANT: MongoDB ajoute automatiquement un champ _id si absent
# _id = Identifiant unique (type: ObjectId)

# Vérifier si l'insertion a réussi:
if result.acknowledged:
    print("[OK] Insertion réussie!")


# === INSÉRER PLUSIEURS DOCUMENTS (insert_many) ===

users = [
    {"name": "Bob", "email": "bob@mail.com", "age": 30},
    {"name": "Charlie", "email": "charlie@mail.com", "age": 35},
    {"name": "Diana", "email": "diana@mail.com", "age": 28}
]

# Insérer tous les documents
result = collection.insert_many(users)

# result.inserted_ids = Liste des _id insérés
print(f"Nombre de documents insérés: {len(result.inserted_ids)}")
print(f"IDs: {result.inserted_ids}")


# === INSÉRER AVEC _id PERSONNALISÉ ===

# Tu peux spécifier ton propre _id (n'importe quel type)
user = {
    "_id": "alice123",  # String comme _id
    "name": "Alice",
    "email": "alice@mail.com"
}

collection.insert_one(user)

# Ou avec un nombre:
user = {
    "_id": 1001,
    "name": "Bob"
}

collection.insert_one(user)

# ATTENTION: L'_id doit être UNIQUE dans la collection!
# Sinon: DuplicateKeyError


# === GESTION DES ERREURS D'INSERTION ===

from pymongo.errors import DuplicateKeyError

try:
    user = {"_id": 1001, "name": "Alice"}
    collection.insert_one(user)
except DuplicateKeyError:
    print("[X] Erreur: _id déjà existant!")


# === OPTIONS D'INSERTION ===

# Insert avec options:
result = collection.insert_one(
    {"name": "Alice", "email": "alice@mail.com"},
    bypass_document_validation=True  # Ignore la validation de schéma
)

# insert_many avec options:
result = collection.insert_many(
    [{"name": "Bob"}, {"name": "Charlie"}],
    ordered=False  # Continue même si erreur sur un document
)

# ordered=True (par défaut): Arrête au premier échec
# ordered=False: Insère tous les documents valides, ignore les erreurs


# === READ (LIRE DES DOCUMENTS) ===


# === TROUVER TOUS LES DOCUMENTS (find) ===

# Retourne TOUS les documents:
all_users = collection.find()

# all_users = Cursor (itérateur)
# Parcourir les résultats:
for user in all_users:
    print(user)
# Affiche:
# {'_id': ObjectId('...'), 'name': 'Alice', 'email': 'alice@mail.com', ...}
# {'_id': ObjectId('...'), 'name': 'Bob', 'email': 'bob@mail.com', ...}

# Convertir en liste:
users_list = list(collection.find())
print(f"Nombre d'utilisateurs: {len(users_list)}")


# === TROUVER UN SEUL DOCUMENT (find_one) ===

# Retourne le PREMIER document qui correspond
user= collection.find_one({"name": "Alice"})

if user:
    print(f"Utilisateur trouvé: {user}")
    print(f"Email: {user['email']}")
    print(f"Âge: {user['age']}")
else:
    print("Aucun utilisateur trouvé")

# find_one retourne:
# - Le document (dict) si trouvé
# - None si aucun résultat


# === FILTRER LES RÉSULTATS (QUERY) ===

# Trouver par nom:
users = collection.find({"name": "Alice"})

# Trouver par âge:
users = collection.find({"age": 25})

# Trouver par plusieurs critères (AND implicite):
users = collection.find({
    "name": "Alice",
    "age": 25,
    "is_active": True
})
# Équivalent SQL: WHERE name='Alice' AND age=25 AND is_active=TRUE


# === OPÉRATEURS DE COMPARAISON ===

# $eq (égal)
users = collection.find({"age": {"$eq": 25}})
# Équivalent: {"age": 25}

# $ne (différent)
users = collection.find({"age": {"$ne": 25}})
# Équivalent SQL: WHERE age != 25

# $gt (supérieur)
users = collection.find({"age": {"$gt": 25}})
# Équivalent SQL: WHERE age > 25

# $gte (supérieur ou égal)
users = collection.find({"age": {"$gte": 25}})
# Équivalent SQL: WHERE age >= 25

# $lt (inférieur)
users = collection.find({"age": {"$lt": 30}})
# Équivalent SQL: WHERE age < 30

# $lte (inférieur ou égal)
users = collection.find({"age": {"$lte": 30}})
# Équivalent SQL: WHERE age <= 30

# $in (dans la liste)
users = collection.find({"age": {"$in": [25, 30, 35]}})
# Équivalent SQL: WHERE age IN (25, 30, 35)

# $nin (pas dans la liste)
users = collection.find({"age": {"$nin": [25, 30]}})
# Équivalent SQL: WHERE age NOT IN (25, 30)


# === OPÉRATEURS LOGIQUES ===

# $and (ET logique)
users = collection.find({
    "$and": [
        {"age": {"$gte": 25}},
        {"age": {"$lte": 35}}
    ]
})
# Équivalent SQL: WHERE age >= 25 AND age <= 35

# $or (OU logique)
users = collection.find({
    "$or": [
        {"name": "Alice"},
        {"name": "Bob"}
    ]
})
# Équivalent SQL: WHERE name='Alice' OR name='Bob'

# $not (NÉGATION)
users = collection.find({
    "age": {"$not": {"$gte": 30}}
})
# Équivalent SQL: WHERE NOT (age >= 30)

# $nor (NI l'un NI l'autre)
users = collection.find({
    "$nor": [
        {"name": "Alice"},
        {"age": 25}
    ]
})
# Équivalent SQL: WHERE NOT (name='Alice' OR age=25)


# === QUERY SUR CHAMPS IMBRIQUÉS ===

# Document avec objet imbriqué:
user = {
    "name": "Alice",
    "address": {
        "street": "123 Main St",
        "city": "Paris",
        "country": "France"
    }
}

collection.insert_one(user)

# Rechercher par champ imbriqué (notation avec point):
users = collection.find({"address.city": "Paris"})
# Trouve tous les utilisateurs dont address.city = "Paris"

users = collection.find({"address.country": "France"})


# === QUERY SUR TABLEAUX (ARRAYS) ===

# Document avec tableau:
user = {
    "name": "Alice",
    "hobbies": ["coding", "reading", "gaming"]
}

collection.insert_one(user)

# Trouver si un élément est dans le tableau:
users = collection.find({"hobbies": "coding"})
# Trouve tous les utilisateurs qui ont "coding" dans leurs hobbies

# Trouver avec plusieurs éléments ($all):
users = collection.find({"hobbies": {"$all": ["coding", "reading"]}})
# Trouve les utilisateurs qui ont TOUS ces hobbies

# Taille du tableau:
users = collection.find({"hobbies": {"$size": 3}})
# Trouve les utilisateurs qui ont exactement 3 hobbies


# === PROJECTION (SÉLECTIONNER DES CHAMPS) ===

# Par défaut, find() retourne TOUS les champs
# Projection = Sélectionner seulement certains champs

# Inclure seulement certains champs:
users = collection.find(
    {"age": {"$gte": 25}},
    {"name": 1, "email": 1}  # 1 = inclure
)
# Résultat: {'_id': ..., 'name': 'Alice', 'email': 'alice@mail.com'}
# _id est TOUJOURS inclus par défaut

# Exclure _id:
users = collection.find(
    {"age": {"$gte": 25}},
    {"name": 1, "email": 1, "_id": 0}  # 0 = exclure
)
# Résultat: {'name': 'Alice', 'email': 'alice@mail.com'}

# Exclure certains champs:
users = collection.find(
    {},
    {"password": 0, "secret_key": 0}  # 0 = exclure
)
# Retourne tous les champs SAUF password et secret_key

# ATTENTION: Tu ne peux pas mélanger inclusion (1) et exclusion (0)
# SAUF pour _id


# === TRI DES RÉSULTATS (sort) ===

# Trier par âge croissant:
users = collection.find().sort("age", 1)
# 1 = croissant (ascending)

# Trier par âge décroissant:
users = collection.find().sort("age", -1)
# -1 = décroissant (descending)

# Trier par plusieurs champs:
users = collection.find().sort([
    ("age", 1),      # D'abord par âge croissant
    ("name", 1)      # Puis par nom croissant
])

# Avec pymongo.ASCENDING et pymongo.DESCENDING:
from pymongo import ASCENDING, DESCENDING

users = collection.find().sort("age", ASCENDING)
users = collection.find().sort("age", DESCENDING)


# === LIMITER LE NOMBRE DE RÉSULTATS (limit) ===

# Obtenir seulement les 5 premiers résultats:
users = collection.find().limit(5)

# Combiner avec sort:
# Les 3 utilisateurs les plus âgés:
users = collection.find().sort("age", -1).limit(3)


# === SAUTER DES RÉSULTATS (skip) ===

# Sauter les 10 premiers résultats:
users = collection.find().skip(10)

# Pagination (page 2, 10 résultats par page):
page = 2
per_page = 10
users = collection.find().skip((page - 1) * per_page).limit(per_page)


# === COMPTER LES DOCUMENTS ===

# Compter tous les documents:
count = collection.count_documents({})
print(f"Nombre total d'utilisateurs: {count}")

# Compter avec filtre:
count = collection.count_documents({"age": {"$gte": 25}})
print(f"Utilisateurs de 25 ans et plus: {count}")

# Compter avec limite (plus rapide):
count = collection.estimated_document_count()
# Estimation rapide (peut être imprécis)


# === VÉRIFIER L'EXISTENCE D'UN DOCUMENT ===

# Méthode 1: Compter
exists = collection.count_documents({"name": "Alice"}, limit=1) > 0

# Méthode 2: find_one
user = collection.find_one({"name": "Alice"})
exists = user is not None


# === UPDATE (METTRE À JOUR DES DOCUMENTS) ===


# === METTRE À JOUR UN DOCUMENT (update_one) ===

# Mise à jour simple avec $set:
result = collection.update_one(
    {"name": "Alice"},           # Filtre: quel document?
    {"$set": {"age": 26}}        # Mise à jour: quoi changer?
)

# result contient:
print(f"Documents correspondants: {result.matched_count}")
print(f"Documents modifiés: {result.modified_count}")

# matched_count = Nombre de documents trouvés
# modified_count = Nombre de documents réellement modifiés

# Si aucun document trouvé:
# matched_count = 0, modified_count = 0


# === METTRE À JOUR PLUSIEURS DOCUMENTS (update_many) ===

# Met à jour TOUS les documents qui correspondent
result = collection.update_many(
    {"age": {"$lt": 30}},        # Tous les < 30 ans
    {"$set": {"is_young": True}} # Ajouter un champ
)

print(f"Documents modifiés: {result.modified_count}")


# === OPÉRATEURS DE MISE À JOUR ===

# $set (Définir une valeur)
collection.update_one(
    {"name": "Alice"},
    {"$set": {"age": 26, "email": "alice@newmail.com"}}
)
# Crée le champ s'il n'existe pas, sinon le modifie

# $unset (Supprimer un champ)
collection.update_one(
    {"name": "Alice"},
    {"$unset": {"middle_name": ""}}  # Supprime le champ middle_name
)

# $inc (Incrémenter une valeur numérique)
collection.update_one(
    {"name": "Alice"},
    {"$inc": {"age": 1}}  # age = age + 1
)

# Décrémenter:
collection.update_one(
    {"name": "Alice"},
    {"$inc": {"age": -1}}  # age = age - 1
)

# $mul (Multiplier)
collection.update_one(
    {"name": "Alice"},
    {"$mul": {"score": 2}}  # score = score * 2
)

# $rename (Renommer un champ)
collection.update_one(
    {"name": "Alice"},
    {"$rename": {"email": "email_address"}}
)

# $min (Mettre à jour si nouvelle valeur est plus petite)
collection.update_one(
    {"name": "Alice"},
    {"$min": {"age": 20}}  # age = min(age actuel, 20)
)

# $max (Mettre à jour si nouvelle valeur est plus grande)
collection.update_one(
    {"name": "Alice"},
    {"$max": {"age": 30}}  # age = max(age actuel, 30)
)

# $currentDate (Mettre la date/heure actuelle)
collection.update_one(
    {"name": "Alice"},
    {"$currentDate": {"last_modified": True}}
)


# === OPÉRATEURS POUR TABLEAUX ===

# $push (Ajouter un élément à un tableau)
collection.update_one(
    {"name": "Alice"},
    {"$push": {"hobbies": "photography"}}
)
# Ajoute "photography" à la fin du tableau hobbies

# $addToSet (Ajouter uniquement si pas déjà présent)
collection.update_one(
    {"name": "Alice"},
    {"$addToSet": {"hobbies": "coding"}}
)
# Ajoute "coding" SEULEMENT s'il n'est pas déjà dans hobbies

# $pop (Retirer le premier ou dernier élément)
collection.update_one(
    {"name": "Alice"},
    {"$pop": {"hobbies": 1}}   # 1 = dernier, -1 = premier
)

# $pull (Retirer un élément spécifique)
collection.update_one(
    {"name": "Alice"},
    {"$pull": {"hobbies": "coding"}}
)
# Retire "coding" du tableau hobbies

# $pullAll (Retirer plusieurs éléments)
collection.update_one(
    {"name": "Alice"},
    {"$pullAll": {"hobbies": ["coding", "gaming"]}}
)


# === UPSERT (UPDATE OU INSERT) ===

# Upsert = Si le document existe: met à jour, sinon: insère
result = collection.update_one(
    {"name": "Eve"},
    {"$set": {"age": 22, "email": "eve@mail.com"}},
    upsert=True
)

# Si "Eve" existe: met à jour
# Si "Eve" n'existe pas: insère un nouveau document

# Vérifier si c'était un insert:
if result.upserted_id:
    print(f"Document inséré avec _id: {result.upserted_id}")
else:
    print("Document mis à jour")


# === REPLACE (REMPLACER UN DOCUMENT) ===

# replace_one = Remplace TOUT le document (sauf _id)
new_user = {
    "name": "Alice",
    "email": "alice@newmail.com",
    "age": 27
}

result = collection.replace_one(
    {"name": "Alice"},
    new_user
)

# ATTENTION: Les champs non présents dans new_user sont SUPPRIMÉS!
# Différence avec update_one + $set:
# - update_one + $set: Modifie seulement les champs spécifiés
# - replace_one: Remplace TOUT le document


# === DELETE (SUPPRIMER DES DOCUMENTS) ===


# === SUPPRIMER UN DOCUMENT (delete_one) ===

result = collection.delete_one({"name": "Alice"})

print(f"Documents supprimés: {result.deleted_count}")

# deleted_count = Nombre de documents supprimés
# Si aucun document trouvé: deleted_count = 0


# === SUPPRIMER PLUSIEURS DOCUMENTS (delete_many) ===

# Supprimer tous les utilisateurs de moins de 25 ans:
result = collection.delete_many({"age": {"$lt": 25}})

print(f"Documents supprimés: {result.deleted_count}")

# Supprimer TOUS les documents:
result = collection.delete_many({})
# [ATTENTION] DANGER: Supprime tout! Utilise avec précaution


# === SUPPRIMER ET RETOURNER (find_one_and_delete) ===

# Supprime et retourne le document supprimé:
deleted_user = collection.find_one_and_delete({"name": "Alice"})

if deleted_user:
    print(f"Utilisateur supprimé: {deleted_user}")
else:
    print("Aucun utilisateur trouvé")


# === SUPPRIMER UNE COLLECTION ENTIÈRE ===

# Supprimer la collection "users":
db.users.drop()

# La collection est complètement supprimée
# Nécessite de la recréer si besoin


# === SUPPRIMER UNE BASE DE DONNÉES ===

# Supprimer la base "myapp_db":
client.drop_database("myapp_db")

# [ATTENTION] TRÈS DANGEREUX: Supprime toute la base!


[OK] REQUÊTES AVANCÉES

# === RECHERCHE PAR REGEX (EXPRESSIONS RÉGULIÈRES) ===

# Trouver les noms qui commencent par "A":
users = collection.find({"name": {"$regex": "^A"}})

# Trouver les emails qui contiennent "gmail":
users = collection.find({"email": {"$regex": "gmail"}})

# Insensible à la casse:
users = collection.find({
    "name": {"$regex": "alice", "$options": "i"}
})
# Trouve "Alice", "ALICE", "alice", etc

# Options regex:
# "i" = insensible à la casse
# "m" = multi-ligne
# "x" = ignorer les espaces
# "s" = point correspond à tout (y compris newline)


# === RECHERCHE AVEC $exists ===

# Trouver les documents qui ONT un champ:
users = collection.find({"middle_name": {"$exists": True}})

# Trouver les documents qui N'ONT PAS un champ:
users = collection.find({"middle_name": {"$exists": False}})


# === RECHERCHE AVEC $type ===

# Trouver par type de données:
users = collection.find({"age": {"$type": "int"}})
users = collection.find({"name": {"$type": "string"}})
users = collection.find({"is_active": {"$type": "bool"}})

# Types BSON:
# "double", "string", "object", "array", "binData",
# "objectId", "bool", "date", "null", "regex",
# "int", "long", "decimal"


# === RECHERCHE AVEC $elemMatch (TABLEAUX) ===

# Document avec tableau d'objets:
user = {
    "name": "Alice",
    "orders": [
        {"item": "book", "price": 15},
        {"item": "pen", "price": 2}
    ]
}

collection.insert_one(user)

# Trouver les utilisateurs avec une commande spécifique:
users = collection.find({
    "orders": {
        "$elemMatch": {
            "item": "book",
            "price": {"$gte": 10}
        }
    }
})
# Trouve les utilisateurs qui ont commandé "book" avec prix >= 10


# === RECHERCHE AVEC $where (JAVASCRIPT) ===

# Exécuter du code JavaScript pour filtrer:
users = collection.find({
    "$where": "this.age > 25 && this.name.length > 5"
})

# ATTENTION: Très lent! Évite si possible
# Préfère les opérateurs MongoDB standards


# === RECHERCHE FULL-TEXT (TEXTE INTÉGRAL) ===

# Créer un index texte d'abord:
collection.create_index([("description", "text")])

# Rechercher du texte:
results = collection.find({"$text": {"$search": "python programming"}})

# Recherche avec score de pertinence:
results = collection.find(
    {"$text": {"$search": "python programming"}},
    {"score": {"$meta": "textScore"}}
).sort([("score", {"$meta": "textScore"})])


# === AGRÉGATION (AGGREGATION PIPELINE) ===

# L'agrégation = Traitement de données en plusieurs étapes
# Comme un pipeline de transformation

# === PIPELINE SIMPLE ===

pipeline = [
    # Étape 1: Filtrer
    {"$match": {"age": {"$gte": 25}}},
    
    # Étape 2: Trier
    {"$sort": {"age": -1}},
    
    # Étape 3: Limiter
    {"$limit": 5}
]

results = collection.aggregate(pipeline)

for result in results:
    print(result)


# === $group (REGROUPER) ===

# Compter les utilisateurs par âge:
pipeline = [
    {"$group": {
        "_id": "$age",              # Regrouper par age
        "count": {"$sum": 1}        # Compter
    }}
]

results = collection.aggregate(pipeline)
# Résultat:
# {'_id': 25, 'count': 3}
# {'_id': 30, 'count': 2}

# Moyenne d'âge:
pipeline = [
    {"$group": {
        "_id": None,                # Un seul groupe
        "average_age": {"$avg": "$age"}
    }}
]


# === $project (TRANSFORMER) ===

# Sélectionner/transformer des champs:
pipeline = [
    {"$project": {
        "name": 1,                  # Inclure name
        "email": 1,                 # Inclure email
        "age_in_months": {"$multiply": ["$age", 12]}  # Calculer
    }}
]

results = collection.aggregate(pipeline)
# Résultat:
# {'_id': ..., 'name': 'Alice', 'email': '...', 'age_in_months': 300}


# === $lookup (JOIN) ===

# Joindre deux collections (comme JOIN en SQL)

# Collection "users":
# {"_id": 1, "name": "Alice"}

# Collection "orders":
# {"_id": 101, "user_id": 1, "item": "book"}

pipeline = [
    {"$lookup": {
        "from": "orders",           # Collection à joindre
        "localField": "_id",        # Champ de users
        "foreignField": "user_id",  # Champ de orders
        "as": "user_orders"         # Nom du nouveau champ
    }}
]

results = db.users.aggregate(pipeline)
# Résultat:
# {
#   "_id": 1,
#   "name": "Alice",
#   "user_orders": [
#     {"_id": 101, "user_id": 1, "item": "book"}
#   ]
# }


# === $unwind (DÉPLIER UN TABLEAU) ===

# Transformer un document avec tableau en plusieurs documents

# Document:
# {"name": "Alice", "hobbies": ["coding", "reading", "gaming"]}

pipeline = [
    {"$unwind": "$hobbies"}
]

results = collection.aggregate(pipeline)
# Résultat:
# {"name": "Alice", "hobbies": "coding"}
# {"name": "Alice", "hobbies": "reading"}
# {"name": "Alice", "hobbies": "gaming"}


# === OPÉRATEURS D'AGRÉGATION ===

# Opérateurs mathématiques:
# $add, $subtract, $multiply, $divide, $mod, $abs, $ceil, $floor

# Opérateurs de chaînes:
# $concat, $substr, $toLower, $toUpper, $split

# Opérateurs de tableaux:
# $size, $arrayElemAt, $slice, $filter

# Opérateurs de date:
# $year, $month, $dayOfMonth, $hour, $minute, $second

# Opérateurs de groupement:
# $sum, $avg, $min, $max, $first, $last, $push, $addToSet


# === EXEMPLE COMPLET D'AGRÉGATION ===

# Statistiques des utilisateurs par pays:
pipeline = [
    # Étape 1: Filtrer les utilisateurs actifs
    {"$match": {"is_active": True}},
    
    # Étape 2: Regrouper par pays
    {"$group": {
        "_id": "$address.country",
        "total_users": {"$sum": 1},
        "average_age": {"$avg": "$age"},
        "oldest_user": {"$max": "$age"},
        "youngest_user": {"$min": "$age"}
    }},
    
    # Étape 3: Trier par nombre d'utilisateurs
    {"$sort": {"total_users": -1}},
    
    # Étape 4: Limiter aux 10 premiers pays
    {"$limit": 10},
    
    # Étape 5: Formater le résultat
    {"$project": {
        "_id": 0,
        "country": "$_id",
        "total_users": 1,
        "average_age": {"$round": ["$average_age", 1]},
        "age_range": {
            "$concat": [
                {"$toString": "$youngest_user"},
                " - ",
                {"$toString": "$oldest_user"}
            ]
        }
    }}
]

results = collection.aggregate(pipeline)

for result in results:
    print(result)
# Résultat:
# {'country': 'France', 'total_users': 150, 'average_age': 28.5, 'age_range': '20 - 45'}
# {'country': 'Germany', 'total_users': 120, 'average_age': 30.2, 'age_range': '22 - 50'}


[OK] INDEXATION (PERFORMANCE)

# === POURQUOI LES INDEX? ===

# Sans index:
# MongoDB doit scanner TOUS les documents pour trouver ce que tu cherches
# Exemple: Trouver {"name": "Alice"} dans 1 million de documents
# = MongoDB vérifie les 1 million de documents un par un
# = TRÈS LENT!

# Avec index:
# MongoDB crée une structure de données spéciale (B-tree)
# = Trouve les documents en quelques opérations seulement
# = TRÈS RAPIDE!

# Analogie:
# Sans index = Chercher un mot dans un livre page par page
# Avec index = Utiliser l'index à la fin du livre


# === CRÉER UN INDEX ===

# Index simple sur un champ:
collection.create_index("email")

# MongoDB crée un index sur le champ "email"
# Les recherches par email seront maintenant très rapides

# Index avec ordre:
collection.create_index([("age", 1)])   # 1 = croissant
collection.create_index([("age", -1)])  # -1 = décroissant


# === INDEX COMPOSÉ (PLUSIEURS CHAMPS) ===

# Index sur plusieurs champs:
collection.create_index([
    ("age", 1),
    ("name", 1)
])

# Utile pour les requêtes qui filtrent sur les deux champs:
users = collection.find({"age": 25, "name": "Alice"})
# Cette requête sera très rapide!


# === INDEX UNIQUE ===

# Garantir l'unicité d'un champ:
collection.create_index("email", unique=True)

# Maintenant, deux documents ne peuvent pas avoir le même email
# Essayer d'insérer un email en double = erreur DuplicateKeyError

# Exemple:
collection.insert_one({"email": "alice@mail.com"})
collection.insert_one({"email": "alice@mail.com"})  # [X] Erreur!


# === INDEX PARTIEL ===

# Indexer seulement certains documents:
collection.create_index(
    "email",
    partialFilterExpression={"age": {"$gte": 18}}
)

# Cet index s'applique seulement aux documents où age >= 18
# Économise de l'espace!


# === INDEX TTL (TIME TO LIVE) ===

# Supprimer automatiquement les documents après un certain temps:
collection.create_index(
    "created_at",
    expireAfterSeconds=3600  # 1 heure
)

# Les documents seront automatiquement supprimés 1 heure après created_at
# Utile pour:
# - Sessions temporaires
# - Logs
# - Cache


# === INDEX TEXTE (FULL-TEXT SEARCH) ===

# Pour recherche de texte intégral:
collection.create_index([("description", "text")])

# Permet des recherches comme:
results = collection.find({"$text": {"$search": "python programming"}})


# === INDEX GÉOSPATIAL ===

# Pour recherche géographique (coordonnées):
collection.create_index([("location", "2dsphere")])

# Document avec coordonnées:
place = {
    "name": "Eiffel Tower",
    "location": {
        "type": "Point",
        "coordinates": [2.2945, 48.8584]  # [longitude, latitude]
    }
}

# Recherche par proximité:
results = collection.find({
    "location": {
        "$near": {
            "$geometry": {
                "type": "Point",
                "coordinates": [2.3, 48.86]
            },
            "$maxDistance": 5000  # 5km
        }
    }
})


# === LISTER LES INDEX ===

# Voir tous les index d'une collection:
indexes = collection.list_indexes()

for index in indexes:
    print(index)

# Ou simplement:
print(collection.index_information())


# === SUPPRIMER UN INDEX ===

# Supprimer un index par nom:
collection.drop_index("email_1")

# Supprimer tous les index (sauf _id):
collection.drop_indexes()


# === ANALYSER LES PERFORMANCES (EXPLAIN) ===

# Voir comment MongoDB exécute une requête:
explain_result = collection.find({"email": "alice@mail.com"}).explain()

print(explain_result)

# Informations importantes:
# - executionStats.executionTimeMillis = Temps d'exécution
# - executionStats.totalDocsExamined = Nombre de documents examinés
# - winningPlan.inputStage.indexName = Index utilisé

# Exemple sans index:
# totalDocsExamined: 100000 (scan complet!)
# executionTimeMillis: 500ms

# Exemple avec index:
# totalDocsExamined: 1 (un seul document!)
# executionTimeMillis: 5ms


# === QUAND CRÉER UN INDEX? ===

# [OK] Créer un index si:
# - Le champ est fréquemment utilisé dans find()
# - Le champ est utilisé dans sort()
# - Le champ doit être unique
# - Les requêtes sur ce champ sont lentes

# [X] Ne PAS créer d'index si:
# - Le champ est rarement utilisé
# - La collection est petite (< 1000 documents)
# - Beaucoup d'écritures (insert/update/delete)
#   (les index ralentissent les écritures)


[OK] TRANSACTIONS (ACID)

# === QU'EST-CE QU'UNE TRANSACTION? ===

# Transaction = Groupe d'opérations qui doivent toutes réussir ou toutes échouer
# ACID = Atomicité, Cohérence, Isolation, Durabilité

# Exemple bancaire:
# Transférer 100€ du compte A au compte B:
# 1. Retirer 100€ du compte A
# 2. Ajouter 100€ au compte B
# 
# Problème: Et si l'étape 2 échoue après l'étape 1?
# = L'argent disparaît!
#
# Solution: Transaction
# = Les deux opérations réussissent ensemble ou échouent ensemble

# IMPORTANT: Les transactions nécessitent:
# - MongoDB 4.0+ (standalone)
# - MongoDB 4.2+ (sharded clusters)
# - Replica Set configuré


# === CRÉER UNE TRANSACTION ===

from pymongo import MongoClient
from pymongo.errors import ConnectionFailure

client = MongoClient("mongodb://localhost:27017/")
db = client["bank_db"]

# Démarrer une session:
with client.start_session() as session:
    # Démarrer la transaction:
    with session.start_transaction():
        try:
            #Opération 1: Retirer de A
            db.accounts.update_one(
                {"account_id": "A"},
                {"$inc": {"balance": -100}},
                session=session
            )
            
            # Opération 2: Ajouter à B
            db.accounts.update_one(
                {"account_id": "B"},
                {"$inc": {"balance": 100}},
                session=session
            )
            
            # Tout a réussi: commit automatique
            print("[OK] Transaction réussie!")
            
        except Exception as e:
            # Erreur: rollback automatique
            print(f"[X] Transaction annulée: {e}")


# === TRANSACTION MANUELLE ===

# Plus de contrôle sur commit/abort:

session = client.start_session()

session.start_transaction()

try:
    # Opérations...
    db.accounts.update_one(
        {"account_id": "A"},
        {"$inc": {"balance": -100}},
        session=session
    )
    
    db.accounts.update_one(
        {"account_id": "B"},
        {"$inc": {"balance": 100}},
        session=session
    )
    
    # Commit manuel:
    session.commit_transaction()
    print("[OK] Transaction committed!")
    
except Exception as e:
    # Rollback manuel:
    session.abort_transaction()
    print(f"[X] Transaction aborted: {e}")
    
finally:
    session.end_session()


# === OPTIONS DE TRANSACTION ===

from pymongo.read_concern import ReadConcern
from pymongo.write_concern import WriteConcern

# Configurer les garanties:
with client.start_session() as session:
    with session.start_transaction(
        read_concern=ReadConcern("snapshot"),    # Isolement
        write_concern=WriteConcern("majority"),  # Durabilité
        read_preference=ReadPreference.PRIMARY   # Lire du primaire
    ):
        # Opérations...
        pass


[OK] GESTION DES ERREURS

# === TYPES D'ERREURS COMMUNES ===

from pymongo.errors import (
    ConnectionFailure,
    DuplicateKeyError,
    WriteError,
    OperationFailure,
    ServerSelectionTimeoutError
)


# === ERREUR DE CONNEXION ===

try:
    client = MongoClient(
        "mongodb://localhost:27017/",
        serverSelectionTimeoutMS=5000
    )
    client.admin.command('ping')
    
except ConnectionFailure as e:
    print(f"[X] Impossible de se connecter: {e}")
    
except ServerSelectionTimeoutError as e:
    print(f"[X] Timeout: serveur inaccessible: {e}")


# === ERREUR DE CLÉ DUPLIQUÉE ===

try:
    collection.insert_one({"_id": 1, "name": "Alice"})
    collection.insert_one({"_id": 1, "name": "Bob"})  # [X] _id existe déjà!
    
except DuplicateKeyError as e:
    print(f"[X] Clé dupliquée: {e}")


# === ERREUR D'ÉCRITURE ===

try:
    # Validation de schéma échoue:
    collection.insert_one({"age": "invalid"})  # age doit être un nombre
    
except WriteError as e:
    print(f"[X] Erreur d'écriture: {e}")


# === ERREUR D'OPÉRATION ===

try:
    # Commande invalide:
    db.command("invalidCommand")
    
except OperationFailure as e:
    print(f"[X] Opération échouée: {e}")


# === GESTION GLOBALE DES ERREURS ===

from pymongo.errors import PyMongoError

try:
    # Opérations MongoDB...
    collection.insert_one({"name": "Alice"})
    
except PyMongoError as e:
    # Capture TOUTES les erreurs MongoDB
    print(f"[X] Erreur MongoDB: {e}")
    # Logger l'erreur, notifier, etc


[OK] VALIDATION DE SCHÉMA

# === POURQUOI VALIDER? ===

# MongoDB est flexible (schéma dynamique)
# Mais parfois tu veux imposer des règles:
# - Champ obligatoire
# - Type de données spécifique
# - Valeur dans une plage

# === CRÉER UNE VALIDATION ===

# Définir les règles de validation:
validator = {
    "$jsonSchema": {
        "bsonType": "object",
        "required": ["name", "email", "age"],  # Champs obligatoires
        "properties": {
            "name": {
                "bsonType": "string",
                "description": "doit être une chaîne et est obligatoire"
            },
            "email": {
                "bsonType": "string",
                "pattern": "^.+@.+$",  # Regex pour email
                "description": "doit être un email valide"
            },
            "age": {
                "bsonType": "int",
                "minimum": 0,
                "maximum": 120,
                "description": "doit être un entier entre 0 et 120"
            },
            "status": {
                "enum": ["active", "inactive", "pending"],
                "description": "peut seulement être active, inactive ou pending"
            }
        }
    }
}

# Créer la collection avec validation:
db.create_collection(
    "users",
    validator=validator,
    validationLevel="strict",  # strict ou moderate
    validationAction="error"   # error ou warn
)

# validationLevel:
# - "strict": Valide tous les inserts et updates
# - "moderate": Valide seulement les documents déjà valides

# validationAction:
# - "error": Refuse l'opération
# - "warn": Log un warning mais accepte


# === MODIFIER LA VALIDATION ===

# Mettre à jour les règles:
db.command({
    "collMod": "users",
    "validator": validator,
    "validationLevel": "moderate"
})


# === EXEMPLE D'INSERTION AVEC VALIDATION ===

# [OK] Insertion valide:
user = {
    "name": "Alice",
    "email": "alice@mail.com",
    "age": 25,
    "status": "active"
}

collection.insert_one(user)  # Réussit

# [X] Insertion invalide:
user = {
    "name": "Bob",
    # "email" manquant!
    "age": 25
}

try:
    collection.insert_one(user)
except WriteError as e:
    print(f"[X] Validation échouée: {e}")


[OK] MONGODB AVEC PYTHON (EXEMPLES COMPLETS)

# === EXEMPLE 1: APPLICATION TODO LIST ===

from pymongo import MongoClient
from datetime import datetime
from bson import ObjectId

class TodoApp:
    def __init__(self):
        self.client = MongoClient("mongodb://localhost:27017/")
        self.db = self.client["todo_db"]
        self.collection = self.db["tasks"]
        
        # Créer un index sur created_at
        self.collection.create_index("created_at")
    
    def add_task(self, title, description="", priority="medium"):
        """Ajouter une nouvelle tâche"""
        task = {
            "title": title,
            "description": description,
            "priority": priority,
            "completed": False,
            "created_at": datetime.now(),
            "updated_at": datetime.now()
        }
        
        result = self.collection.insert_one(task)
        print(f"[OK] Tâche ajoutée avec _id: {result.inserted_id}")
        return result.inserted_id
    
    def get_all_tasks(self, completed=None):
        """Obtenir toutes les tâches"""
        query = {}
        if completed is not None:
            query["completed"] = completed
        
        tasks = self.collection.find(query).sort("created_at", -1)
        return list(tasks)
    
    def get_task(self, task_id):
        """Obtenir une tâche par ID"""
        return self.collection.find_one({"_id": ObjectId(task_id)})
    
    def update_task(self, task_id, **kwargs):
        """Mettre à jour une tâche"""
        kwargs["updated_at"] = datetime.now()
        
        result = self.collection.update_one(
            {"_id": ObjectId(task_id)},
            {"$set": kwargs}
        )
        
        if result.modified_count > 0:
            print("[OK] Tâche mise à jour!")
            return True
        else:
            print("[X] Tâche non trouvée")
            return False
    
    def mark_completed(self, task_id):
        """Marquer une tâche comme complétée"""
        return self.update_task(task_id, completed=True)
    
    def delete_task(self, task_id):
        """Supprimer une tâche"""
        result = self.collection.delete_one({"_id": ObjectId(task_id)})
        
        if result.deleted_count > 0:
            print("[OK] Tâche supprimée!")
            return True
        else:
            print("[X] Tâche non trouvée")
            return False
    
    def get_statistics(self):
        """Statistiques sur les tâches"""
        pipeline = [
            {
                "$group": {
                    "_id": "$completed",
                    "count": {"$sum": 1}
                }
            }
        ]
        
        stats = list(self.collection.aggregate(pipeline))
        
        total = sum(s["count"] for s in stats)
        completed = next((s["count"] for s in stats if s["_id"]), 0)
        pending = total - completed
        
        return {
            "total": total,
            "completed": completed,
            "pending": pending
        }
    
    def close(self):
        """Fermer la connexion"""
        self.client.close()


# Utilisation:
app = TodoApp()

# Ajouter des tâches:
app.add_task("Apprendre MongoDB", "Lire la documentation", "high")
app.add_task("Faire les courses", priority="medium")
app.add_task("Appeler maman", priority="low")

# Afficher toutes les tâches:
tasks = app.get_all_tasks()
for task in tasks:
    status = "[OK]" if task["completed"] else "[X]"
    print(f"{status} {task['title']} - {task['priority']}")

# Marquer une tâche comme complétée:
if tasks:
    app.mark_completed(str(tasks[0]["_id"]))

# Statistiques:
stats = app.get_statistics()
print(f"Total: {stats['total']}, Complétées: {stats['completed']}, En cours: {stats['pending']}")

# Fermer:
app.close()


# === EXEMPLE 2: BLOG AVEC COMMENTAIRES ===

from pymongo import MongoClient
from datetime import datetime
from bson import ObjectId

class BlogApp:
    def __init__(self):
        self.client = MongoClient("mongodb://localhost:27017/")
        self.db = self.client["blog_db"]
        self.posts = self.db["posts"]
        self.comments = self.db["comments"]
        
        # Index pour recherche full-text:
        self.posts.create_index([("title", "text"), ("content", "text")])
    
    def create_post(self, title, content, author, tags=[]):
        """Créer un nouvel article"""
        post = {
            "title": title,
            "content": content,
            "author": author,
            "tags": tags,
            "created_at": datetime.now(),
            "updated_at": datetime.now(),
            "views": 0,
            "likes": 0
        }
        
        result = self.posts.insert_one(post)
        print(f"[OK] Article créé avec _id: {result.inserted_id}")
        return result.inserted_id
    
    def get_post(self, post_id):
        """Obtenir un article et incrémenter les vues"""
        post = self.posts.find_one_and_update(
            {"_id": ObjectId(post_id)},
            {"$inc": {"views": 1}},
            return_document=True
        )
        return post
    
    def add_comment(self, post_id, author, content):
        """Ajouter un commentaire à un article"""
        comment = {
            "post_id": ObjectId(post_id),
            "author": author,
            "content": content,
            "created_at": datetime.now(),
            "likes": 0
        }
        
        result = self.comments.insert_one(comment)
        print(f"[OK] Commentaire ajouté!")
        return result.inserted_id
    
    def get_post_with_comments(self, post_id):
        """Obtenir un article avec tous ses commentaires"""
        pipeline = [
            # Filtrer l'article:
            {"$match": {"_id": ObjectId(post_id)}},
            
            # Joindre les commentaires:
            {"$lookup": {
                "from": "comments",
                "localField": "_id",
                "foreignField": "post_id",
                "as": "comments"
            }},
            
            # Trier les commentaires:
            {"$addFields": {
                "comments": {
                    "$sortArray": {
                        "input": "$comments",
                        "sortBy": {"created_at": -1}
                    }
                }
            }}
        ]
        
        result = list(self.posts.aggregate(pipeline))
        return result[0] if result else None
    
    def search_posts(self, query):
        """Rechercher des articles par texte"""
        results = self.posts.find(
            {"$text": {"$search": query}},
            {"score": {"$meta": "textScore"}}
        ).sort([("score", {"$meta": "textScore"})])
        
        return list(results)
    
    def get_popular_posts(self, limit=5):
        """Obtenir les articles les plus populaires"""
        return list(self.posts.find().sort("views", -1).limit(limit))
    
    def like_post(self, post_id):
        """Liker un article"""
        self.posts.update_one(
            {"_id": ObjectId(post_id)},
            {"$inc": {"likes": 1}}
        )
    
    def close(self):
        """Fermer la connexion"""
        self.client.close()


# Utilisation:
blog = BlogApp()

# Créer des articles:
post1 = blog.create_post(
    "Introduction à MongoDB",
    "MongoDB est une base de données NoSQL...",
    "Alice",
    tags=["mongodb", "database", "nosql"]
)

post2 = blog.create_post(
    "Python pour débutants",
    "Python est un langage de programmation...",
    "Bob",
    tags=["python", "programming", "tutorial"]
)

# Ajouter des commentaires:
blog.add_comment(str(post1), "Charlie", "Super article!")
blog.add_comment(str(post1), "Diana", "Très utile, merci!")

# Obtenir un article avec commentaires:
full_post = blog.get_post_with_comments(str(post1))
print(f"Titre: {full_post['title']}")
print(f"Vues: {full_post['views']}")
print(f"Commentaires: {len(full_post['comments'])}")

for comment in full_post['comments']:
    print(f"  - {comment['author']}: {comment['content']}")

# Rechercher:
results = blog.search_posts("MongoDB database")
print(f"Résultats de recherche: {len(results)}")

# Fermer:
blog.close()


[OK] MONGODB AVEC FLASK (API REST)

# === APPLICATION WEB COMPLÈTE ===

from flask import Flask, request, jsonify
from pymongo import MongoClient
from bson import ObjectId, json_util
import json

app = Flask(__name__)

# Connexion MongoDB:
client = MongoClient("mongodb://localhost:27017/")
db = client["api_db"]
collection = db["users"]


# Helper pour convertir ObjectId en JSON:
def parse_json(data):
    return json.loads(json_util.dumps(data))


# === ROUTES CRUD ===

# CREATE - Créer un utilisateur:
@app.route("/users", methods=["POST"])
def create_user():
    data = request.get_json()
    
    # Validation basique:
    if not data.get("name") or not data.get("email"):
        return jsonify({"error": "name et email requis"}), 400
    
    # Insérer:
    result = collection.insert_one(data)
    
    # Retourner l'utilisateur créé:
    user = collection.find_one({"_id": result.inserted_id})
    return jsonify(parse_json(user)), 201


# READ - Obtenir tous les utilisateurs:
@app.route("/users", methods=["GET"])
def get_users():
    # Pagination:
    page = int(request.args.get("page", 1))
    per_page = int(request.args.get("per_page", 10))
    
    skip = (page - 1) * per_page
    
    users = collection.find().skip(skip).limit(per_page)
    total = collection.count_documents({})
    
    return jsonify({
        "users": parse_json(list(users)),
        "total": total,
        "page": page,
        "per_page": per_page
    })


# READ - Obtenir un utilisateur par ID:
@app.route("/users/<user_id>", methods=["GET"])
def get_user(user_id):
    user = collection.find_one({"_id": ObjectId(user_id)})
    
    if not user:
        return jsonify({"error": "Utilisateur non trouvé"}), 404
    
    return jsonify(parse_json(user))


# UPDATE - Mettre à jour un utilisateur:
@app.route("/users/<user_id>", methods=["PUT"])
def update_user(user_id):
    data = request.get_json()
    
    result = collection.update_one(
        {"_id": ObjectId(user_id)},
        {"$set": data}
    )
    
    if result.matched_count == 0:
        return jsonify({"error": "Utilisateur non trouvé"}), 404
    
    # Retourner l'utilisateur mis à jour:
    user = collection.find_one({"_id": ObjectId(user_id)})
    return jsonify(parse_json(user))


# DELETE - Supprimer un utilisateur:
@app.route("/users/<user_id>", methods=["DELETE"])
def delete_user(user_id):
    result = collection.delete_one({"_id": ObjectId(user_id)})
    
    if result.deleted_count == 0:
        return jsonify({"error": "Utilisateur non trouvé"}), 404
    
    return jsonify({"message": "Utilisateur supprimé"}), 200


# SEARCH - Rechercher des utilisateurs:
@app.route("/users/search", methods=["GET"])
def search_users():
    query = request.args.get("q", "")
    
    users = collection.find({
        "name": {"$regex": query, "$options": "i"}
    })
    
    return jsonify(parse_json(list(users)))


if __name__ == "__main__":
    app.run(debug=True)


# === TESTER L'API (avec curl ou Postman) ===

# Créer un utilisateur:
# curl -X POST http://localhost:5000/users \
#   -H "Content-Type: application/json" \
#   -d '{"name": "Alice", "email": "alice@mail.com", "age": 25}'

# Obtenir tous les utilisateurs:
# curl http://localhost:5000/users

# Obtenir un utilisateur:
# curl http://localhost:5000/users/507f1f77bcf86cd799439011

# Mettre à jour:
# curl -X PUT http://localhost:5000/users/507f1f77bcf86cd799439011 \
#   -H "Content-Type: application/json" \
#   -d '{"age": 26}'

# Supprimer:
# curl -X DELETE http://localhost:5000/users/507f1f77bcf86cd799439011

# Rechercher:
# curl http://localhost:5000/users/search?q=Alice


[OK] BONNES PRATIQUES & SÉCURITÉ

# === CONNEXION SÉCURISÉE ===

# [X] MAL: Identifiants en dur
client = MongoClient("mongodb://admin:password123@localhost:27017/")

# [OK] BIEN: Variables d'environnement
import os
from dotenv import load_dotenv

load_dotenv()

MONGO_URI = os.getenv("MONGO_URI")
client = MongoClient(MONGO_URI)


# === VALIDATION DES ENTRÉES ===

# Toujours valider les données utilisateur!

def create_user(name, email, age):
    # Validation:
    if not name or len(name) < 2:
        raise ValueError("Nom invalide")
    
    if not email or "@" not in email:
        raise ValueError("Email invalide")
    
    if not isinstance(age, int) or age < 0:
        raise ValueError("Âge invalide")
    
    # Insertion:
    user = {"name": name, "email": email, "age": age}
    collection.insert_one(user)


# === PRÉVENIR LES INJECTIONS NoSQL ===

# [X] DANGER: Concaténation de strings
query = {"name": user_input}  # Si user_input = {"$ne": None}, retourne TOUT!

# [OK] SÉCURISÉ: Utiliser les types Python corrects
from bson import ObjectId

def get_user_by_id(user_id):
    # Valider que user_id est bien un ObjectId:
    if not ObjectId.is_valid(user_id):
        raise ValueError("ID invalide")
    
    return collection.find_one({"_id": ObjectId(user_id)})


# === LIMITER LES RÉSULTATS ===

# Toujours limiter le nombre de documents retournés:
users = collection.find().limit(100)

# Ou avec pagination:
def get_paginated_users(page=1, per_page=20):
    skip = (page - 1) * per_page
    users = collection.find().skip(skip).limit(per_page)
    return list(users)


# === UTILISER LES PROJECTIONS ===

# Ne retourner QUE les champs nécessaires:

# [X] MAL: Retourne tout
users = collection.find()

# [OK] BIEN: Retourne seulement name et email
users = collection.find({}, {"name": 1, "email": 1, "_id": 0})


# === GÉRER LES CONNEXIONS ===

# [OK] BIEN: Context manager
with MongoClient(MONGO_URI) as client:
    db = client["mydb"]
    # Faire des opérations...
# Connexion fermée automatiquement

# [OK] BIEN: Pool de connexions (automatique avec pymongo)
client = MongoClient(
    MONGO_URI,
    maxPoolSize=50,      # Max 50 connexions simultanées
    minPoolSize=10,      # Min 10 connexions toujours actives
    maxIdleTimeMS=30000  # Ferme les connexions inactives après 30s
)


# === LOGS & MONITORING ===

import logging

# Configurer les logs MongoDB:
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("pymongo")

# Les logs MongoDB apparaîtront maintenant dans la console


# === BACKUP & RESTORE ===

# Sauvegarder une base de données:
# mongodump --db myapp_db --out /backup/

# Restaurer:
# mongorestore --db myapp_db /backup/myapp_db/

# Sauvegarder une collection spécifique:
# mongodump --db myapp_db --collection users --out /backup/

# Export en JSON:
# mongoexport --db myapp_db --collection users --out users.json

# Import depuis JSON:
# mongoimport --db myapp_db --collection users --file users.json


[OK] DÉPLOIEMENT EN PRODUCTION

# === MONGODB ATLAS (CLOUD) ===

# Avantages:
# - Géré automatiquement (backups, monitoring, scaling)
# - Gratuit jusqu'à 512 MB
# - Disponible mondialement
# - Certificats SSL automatiques

# Connexion Atlas:
uri = "mongodb+srv://username:password@cluster0.xxxxx.mongodb.net/mydb?retryWrites=true&w=majority"

client = MongoClient(
    uri,
    tls=True,                     # SSL/TLS obligatoire
    tlsAllowInvalidCertificates=False,  # Vérifier les certificats
    serverSelectionTimeoutMS=5000
)


# === MONGODB LOCAL AVEC DOCKER ===

# docker-compose.yml:
"""
version: "3.8"

services:
  mongodb:
    image: mongo:7.0
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: secretpassword
    ports:
      - "27017:27017"
    volumes:
      - mongodb_data:/data/db

volumes:
  mongodb_data:
"""

# Démarrer:
# docker-compose up -d

# Connexion:
client = MongoClient(
    "mongodb://admin:secretpassword@localhost:27017/",
    authSource="admin"
)


# === CONFIGURATION PRODUCTION ===

# Fichier config.py:
"""
import os

class Config:
    MONGO_URI = os.getenv("MONGO_URI", "mongodb://localhost:27017/")
    MONGO_DB = os.getenv("MONGO_DB", "myapp_db")
    MONGO_MAX_POOL_SIZE = int(os.getenv("MONGO_MAX_POOL_SIZE", 50))
    MONGO_MIN_POOL_SIZE = int(os.getenv("MONGO_MIN_POOL_SIZE", 10))
"""

# Utilisation:
from config import Config

client = MongoClient(
    Config.MONGO_URI,
    maxPoolSize=Config.MONGO_MAX_POOL_SIZE,
    minPoolSize=Config.MONGO_MIN_POOL_SIZE
)

db = client[Config.MONGO_DB]


[OK] DÉPANNAGE & ERREURS COMMUNES

# === PROBLÈME: "ServerSelectionTimeoutError" ===

# Cause: MongoDB n'est pas accessible
# Solutions:
# 1. Vérifier que MongoDB tourne:
#    brew services list (macOS)
#    sudo systemctl status mongod (Linux)
# 2. Vérifier le port:
#    netstat -an | grep 27017
# 3. Vérifier l'URI:
#    client = MongoClient("mongodb://localhost:27017/")


# === PROBLÈME: "OperationFailure: Authentication failed" ===

# Cause: Mauvais identifiants
# Solution:
# 1. Vérifier username/password
# 2. Vérifier authSource:
client = MongoClient(
    "mongodb://user:password@localhost:27017/",
    authSource="admin"  # Ou "mydb"
)


# === PROBLÈME: "DuplicateKeyError" ===

# Cause: _id ou champ unique déjà existant
# Solutions:
# 1. Ne pas spécifier _id (laisse MongoDB le générer)
# 2. Utiliser upsert pour update ou insert:
collection.update_one(
    {"email": "alice@mail.com"},
    {"$set": {"name": "Alice"}},
    upsert=True
)


# === PROBLÈME: Requêtes lentes ===

# Solutions:
# 1. Créer des index:
collection.create_index("email")

# 2. Utiliser explain() pour analyser:
explain_result = collection.find({"email": "alice@mail.com"}).explain()
print(explain_result["executionStats"]["executionTimeMillis"])

# 3. Limiter les résultats:
users = collection.find().limit(100)

# 4. Utiliser des projections:
users = collection.find({}, {"name": 1, "email": 1})


# === PROBLÈME: "Cursor not found" ===

# Cause: Le curseur a expiré (10 min par défaut)
# Solution: Convertir en liste immédiatement:
users = list(collection.find())


# === PROBLÈME: Données incohérentes après crash ===

# Solution: Utiliser des transactions pour les opérations critiques
with client.start_session() as session:
    with session.start_transaction():
        collection.insert_one(doc1, session=session)
        collection.update_one(filter, update, session=session)


[OK] RESSOURCES & LIENS

# Documentation officielle MongoDB:
# https://docs.mongodb.com/

# Documentation PyMongo:
# https://pymongo.readthedocs.io/

# MongoDB University (cours gratuits):
# https://university.mongodb.com/

# MongoDB Atlas (cloud gratuit):
# https://www.mongodb.com/cloud/atlas

# Cheat Sheet officielle:
# https://www.mongodb.com/developer/products/mongodb/cheat-sheet/

# Forum communautaire:
# https://www.mongodb.com/community/forums/
```