<?php
/**
 * Modèle pour les opérations financières
 */
class OperationModel {
    private $pdo;
    const CATEGORIE_DONS_ID = 3;
    public function __construct($pdo) {
        if (!$pdo) {
            throw new InvalidArgumentException("Connexion à la base de données invalide.");
        }
        $this->pdo = $pdo;
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }
    // =======================================================================
    //   MÉTHODES PUBLIQUES
    // =======================================================================
    public function getAll($excludeDons = false) {
        try {
            $sql = "
                SELECT 
                    o.id, o.montant, o.date_operation, o.mois_cible, o.description, o.type, o.created_at,
                    o.categorie_id, o.membre_id,
                    m.nom as membre_nom, m.prenom as membre_prenom,
                    c.nom as categorie_nom
                FROM operations o
                LEFT JOIN membres m ON o.membre_id = m.id
                LEFT JOIN categories c ON o.categorie_id = c.id
            ";
            
            if ($excludeDons) {
                $sql .= " WHERE o.categorie_id != " . self::CATEGORIE_DONS_ID;
            }
            
            $sql .= " ORDER BY o.date_operation DESC, o.created_at DESC";
            
            $stmt = $this->pdo->query($sql);
            return $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (Exception $e) {
            error_log("Erreur dans OperationModel::getAll: " . $e->getMessage());
            return [];
        }
    }
    public function getById($id) {
        try {
            $sql = "
                SELECT 
                    o.id, o.montant, o.date_operation, o.mois_cible, o.description, o.type, o.created_at,
                    o.categorie_id, o.membre_id,
                    m.nom as membre_nom, m.prenom as membre_prenom,
                    c.nom as categorie_nom
                FROM operations o
                LEFT JOIN membres m ON o.membre_id = m.id
                LEFT JOIN categories c ON o.categorie_id = c.id
                WHERE o.id = ?
            ";
            
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute([$id]);
            return $stmt->fetch(PDO::FETCH_ASSOC);
        } catch (Exception $e) {
            error_log("Erreur dans OperationModel::getById: " . $e->getMessage());
            return null;
        }
    }
    public function getFinancialStats() {
        try {
            // Total Entrées (uniquement type 'entree', exclut 'regularisation')
            $stmtEntrees = $this->pdo->prepare("
                SELECT COALESCE(SUM(montant), 0) as total_entrees 
                FROM operations 
                WHERE type = 'entree' AND categorie_id != ?
            ");
            $stmtEntrees->execute([self::CATEGORIE_DONS_ID]);
            $totalEntrees = $stmtEntrees->fetch(PDO::FETCH_ASSOC)['total_entrees'];
            // Total Sorties
            $stmtSorties = $this->pdo->prepare("
                SELECT COALESCE(SUM(montant), 0) as total_sorties 
                FROM operations 
                WHERE type = 'sortie'
            ");
            $stmtSorties->execute();
            $totalSorties = $stmtSorties->fetch(PDO::FETCH_ASSOC)['total_sorties'];
            // Total Dons
            $stmtDons = $this->pdo->prepare("
                SELECT COALESCE(SUM(montant), 0) as total_dons 
                FROM operations 
                WHERE type = 'entree' AND categorie_id = ?
            ");
            $stmtDons->execute([self::CATEGORIE_DONS_ID]);
            $totalDons = $stmtDons->fetch(PDO::FETCH_ASSOC)['total_dons'];
            // Total Opérations (tous types confondus)
            $stmtOperations = $this->pdo->prepare("
                SELECT COUNT(*) as total_operations 
                FROM operations 
                WHERE categorie_id != ?
            ");
            $stmtOperations->execute([self::CATEGORIE_DONS_ID]);
            $totalOperations = $stmtOperations->fetch(PDO::FETCH_ASSOC)['total_operations'];
            return [
                'total_entrees' => (float)$totalEntrees,
                'total_sorties' => (float)$totalSorties,
                'total_dons' => (float)$totalDons,
                'total_operations' => (int)$totalOperations,
                'solde' => (float)$totalEntrees - (float)$totalSorties
            ];
        } catch (Exception $e) {
            error_log("Erreur dans getFinancialStats: " . $e->getMessage());
            return [
                'total_entrees' => 0, 'total_sorties' => 0, 'total_dons' => 0, 'total_operations' => 0, 'solde' => 0
            ];
        }
    }
    public function getByDateRange($startDate, $endDate, $excludeDons = true) {
        try {
            $sql = "
                SELECT 
                    o.id, o.montant, o.date_operation, o.mois_cible, o.description, o.type, o.created_at,
                    o.categorie_id, o.membre_id,
                    m.nom as membre_nom, m.prenom as membre_prenom,
                    c.nom as categorie_nom
                FROM operations o
                LEFT JOIN membres m ON o.membre_id = m.id
                LEFT JOIN categories c ON o.categorie_id = c.id
                WHERE o.date_operation BETWEEN ? AND ?
            ";
            
            if ($excludeDons) {
                $sql .= " AND o.categorie_id != " . self::CATEGORIE_DONS_ID;
            }
            
            $sql .= " ORDER BY o.date_operation DESC, o.created_at DESC";
            
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute([$startDate, $endDate]);
            return $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (Exception $e) {
            error_log("Erreur dans OperationModel::getByDateRange: " . $e->getMessage());
            return [];
        }
    }
    public function add($type, $categorieId, $membreId, $montant, $dateOperation, $description = null) {
        try {
            $this->pdo->beginTransaction();
            if ($this->isCotisationMensuelle($categorieId)) {
                $result = $this->traiterCotisation($membreId, $montant, $dateOperation, $description);
            } else {
                $stmt = $this->pdo->prepare("
                    INSERT INTO operations (type, categorie_id, membre_id, montant, date_operation, description)
                    VALUES (?, ?, ?, ?, ?, ?)
                ");
                $success = $stmt->execute([$type, $categorieId, $membreId, $montant, $dateOperation, $description]);
                $result = ["success" => $success, "message" => $success ? "Opération enregistrée" : "Échec de l'enregistrement"];
            }
            if ($result['success']) {
                $this->pdo->commit();
                if (!isset($result['id'])) {
                    $result['id'] = $this->pdo->lastInsertId();
                }
            } else {
                $this->pdo->rollBack();
            }
            
            return $result;
        } catch (Exception $e) {
            $this->pdo->rollBack();
            error_log("Erreur dans OperationModel::add - Transaction annulée : " . $e->getMessage());
            return ["success" => false, "message" => "Erreur serveur: " . $e->getMessage()];
        }
    }
    public function update($id, $type, $categorieId, $membreId, $montant, $dateOperation, $description = null) {
        try {
            $this->pdo->beginTransaction();
            
            $stmt = $this->pdo->prepare("
                UPDATE operations 
                SET type = ?, categorie_id = ?, membre_id = ?, montant = ?, date_operation = ?, description = ?
                WHERE id = ?
            ");
            $success = $stmt->execute([$type, $categorieId, $membreId, $montant, $dateOperation, $description, $id]);
            
            $this->pdo->commit();
            return ["success" => $success, "message" => $success ? "Opération mise à jour" : "Échec de la mise à jour"];
        } catch (Exception $e) {
            $this->pdo->rollBack();
            error_log("Erreur dans OperationModel::update - Transaction annulée : " . $e->getMessage());
            return ["success" => false, "message" => "Erreur serveur: " . $e->getMessage()];
        }
    }
    public function delete($id) {
        try {
            $stmt = $this->pdo->prepare("DELETE FROM operations WHERE id = ?");
            $success = $stmt->execute([$id]);
            
            return ["success" => $success, "message" => $success ? "Opération supprimée" : "Échec de la suppression"];
        } catch (Exception $e) {
            error_log("Erreur dans OperationModel::delete: " . $e->getMessage());
            return ["success" => false, "message" => "Erreur serveur: " . $e->getMessage()];
        }
    }
    /**
     * Génère automatiquement les cotisations mensuelles pour un membre à jour
     * depuis Janvier 2025 jusqu'à la date_debut_cotisation définie par l'admin.
     * 
     * @param int $membreId ID du membre
     * @param string $dateDebutCotisation Date au format 'YYYY-MM' (ex: '2026-12')
     * @param bool $comptabiliser Si true, type='entree' (comptabilisé). Si false, type='regularisation' (non comptabilisé).
     * @return array Résultat de l'opération avec success et message
     */
    public function genererCotisationsInitiales($membreId, $dateDebutCotisation, $comptabiliser = true) {
        try {
            $this->pdo->beginTransaction();
            // 1. Vérifier que le membre existe et n'a pas d'arriérés
            $stmtMembre = $this->pdo->prepare("
                SELECT id, montant_arriere, date_debut_arriere 
                FROM membres 
                WHERE id = ?
            ");
            $stmtMembre->execute([$membreId]);
            $membre = $stmtMembre->fetch(PDO::FETCH_ASSOC);
            if (!$membre) {
                throw new Exception("Membre non trouvé.");
            }
            // Si le membre a des arriérés, on ne génère pas automatiquement
            $montantArriere = floatval($membre['montant_arriere'] ?? 0);
            if ($montantArriere > 0) {
                $this->pdo->rollBack();
                return [
                    "success" => false, 
                    "message" => "Le membre a des arriérés. Veuillez d'abord régulariser les arriérés."
                ];
            }
            // 2. Définir la période : Janvier 2025 → date_debut_cotisation
            $dateDebut = new DateTime('2025-01-01'); // Janvier 2025
            $dateFin = DateTime::createFromFormat('Y-m', $dateDebutCotisation);
            
            if (!$dateFin) {
                throw new Exception("Format de date invalide. Utilisez YYYY-MM (ex: 2026-12)");
            }
            // 3. Vérifier qu'il n'y a pas déjà des cotisations générées pour ce membre
            $stmtCheck = $this->pdo->prepare("
                SELECT COUNT(*) as count 
                FROM operations 
                WHERE membre_id = ? 
                AND categorie_id = ? 
                AND mois_cible >= '2025-01'
            ");
            $stmtCheck->execute([$membreId, $this->getCategorieCotisationId()]);
            $existing = $stmtCheck->fetch(PDO::FETCH_ASSOC);
            
            if ($existing['count'] > 0) {
                $this->pdo->rollBack();
                return [
                    "success" => false, 
                    "message" => "Des cotisations existent déjà pour ce membre depuis 2025. Supprimez-les d'abord si nécessaire."
                ];
            }
            // 4. Générer les cotisations mois par mois
            $operationsAInserer = [];
            $currentMonth = clone $dateDebut;
            $moisGeneres = 0;
            $dateVersement = date('Y-m-d'); // Date du jour
            
            // Déterminer le type d'opération
            $typeOperation = $comptabiliser ? 'entree' : 'regularisation';
            while ($currentMonth <= $dateFin) {
                $annee = (int)$currentMonth->format('Y');
                $tarif = ($annee < 2024) ? 200.0 : 250.0;
                $moisCible = $currentMonth->format('Y-m');
                
                $operationsAInserer[] = [
                    'membre_id' => $membreId,
                    'categorie_id' => $this->getCategorieCotisationId(),
                    'montant' => $tarif,
                    'type' => $typeOperation,
                    'date_operation' => $dateVersement,
                    'mois_cible' => $moisCible,
                    'description' => "Cotisation initiale pour $moisCible (générée automatiquement)"
                ];
                
                $moisGeneres++;
                $currentMonth->modify('+1 month');
            }
            // 5. Insertion des opérations générées
            if (!empty($operationsAInserer)) {
                $sql = "INSERT INTO operations (membre_id, categorie_id, montant, type, date_operation, mois_cible, description) VALUES ";
                $placeholders = [];
                $values = [];
                
                foreach ($operationsAInserer as $op) {
                    $placeholders[] = "(?, ?, ?, ?, ?, ?, ?)";
                    $values = array_merge($values, [
                        $op['membre_id'], 
                        $op['categorie_id'], 
                        $op['montant'], 
                        $op['type'], 
                        $op['date_operation'], 
                        $op['mois_cible'], 
                        $op['description']
                    ]);
                }
                
                $sql .= implode(',', $placeholders);
                $stmt = $this->pdo->prepare($sql);
                $stmt->execute($values);
            }
            // 6. Mettre à jour la date_debut_arriere du membre pour le mois suivant
            $nextMonth = clone $dateFin;
            $nextMonth->modify('+1 month');
            $newDateDebut = $nextMonth->format('Y-m-d');
            
            $stmtUpdateMembre = $this->pdo->prepare("
                UPDATE membres 
                SET date_debut_arriere = ? 
                WHERE id = ?
            ");
            $stmtUpdateMembre->execute([$newDateDebut, $membreId]);
            $this->pdo->commit();
            // Formater les noms de mois en français
            $moisFr = [
                1 => 'Janvier', 2 => 'Février', 3 => 'Mars', 4 => 'Avril',
                5 => 'Mai', 6 => 'Juin', 7 => 'Juillet', 8 => 'Août',
                9 => 'Septembre', 10 => 'Octobre', 11 => 'Novembre', 12 => 'Décembre'
            ];
            
            $moisFin = (int)$dateFin->format('m');
            $anneeFin = $dateFin->format('Y');
            $moisSuivant = (int)$nextMonth->format('m');
            $anneeSuivant = $nextMonth->format('Y');
            
            $message = "$moisGeneres cotisation(s) générée(s) automatiquement de Janvier 2025 à " . 
                       $moisFr[$moisFin] . " $anneeFin. L'admin peut maintenant continuer à partir de " . 
                       $moisFr[$moisSuivant] . " $anneeSuivant.";
            return [
                "success" => true, 
                "message" => $message,
                "mois_generes" => $moisGeneres
            ];
        } catch (Exception $e) {
            $this->pdo->rollBack();
            error_log("Erreur dans genererCotisationsInitiales: " . $e->getMessage());
            return [
                "success" => false, 
                "message" => "Erreur serveur: " . $e->getMessage()
            ];
        }
    }
    // =======================================================================
    //   MÉTHODES PRIVÉES (UTILITAIRES INTERNES)
    // =======================================================================
    private function isCotisationMensuelle($categorieId) {
        try {
            $stmt = $this->pdo->prepare("SELECT nom FROM categories WHERE id = :id LIMIT 1");
            $stmt->execute([':id' => $categorieId]);
            $category = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if ($category && strtolower(trim($category['nom'])) === 'cotisations membres') {
                return true;
            }
            return false;
        } catch (Exception $e) {
            error_log("Erreur dans isCotisationMensuelle: " . $e->getMessage());
            return false;
        }
    }
    private function traiterCotisation($membreId, $montantVerse, $dateVersement, $description = null) {
        try {
            $stmtMembre = $this->pdo->prepare("SELECT date_debut_arriere, solde_credit FROM membres WHERE id = ?");
            $stmtMembre->execute([$membreId]);
            $membre = $stmtMembre->fetch(PDO::FETCH_ASSOC);
            if (!$membre) {
                throw new Exception("Membre non trouvé.");
            }
            // 1. Déterminer le mois de départ (Prochain mois à payer)
            if (empty($membre['date_debut_arriere'])) {
                // Si vide, on commence au 1er du mois actuel
                $nextMonthToPay = new DateTime('first day of this month');
            } else {
                $nextMonthToPay = new DateTime($membre['date_debut_arriere']);
            }
            $montantRestant = $montantVerse;
            $operationsAInserer = [];
            $moisCouverts = 0;
            // 2. BOUCLE INFINIE (Tant qu'il y a de l'argent pour payer un mois)
            while (true) {
                $annee = (int)$nextMonthToPay->format('Y');
                // Tarif: 200 avant 2024, 250 après
                $tarif = ($annee < 2024) ? 200.0 : 250.0;
                if ($montantRestant >= $tarif) {
                    // On paie ce mois-ci
                    $moisCible = $nextMonthToPay->format('Y-m');
                    
                    $operationsAInserer[] = [
                        'membre_id' => $membreId,
                        'categorie_id' => $this->getCategorieCotisationId(),
                        'montant' => $tarif,
                        'type' => 'entree',
                        'date_operation' => $dateVersement,
                        'mois_cible' => $moisCible,
                        'description' => ($description ?? "Cotisation pour $moisCible")
                    ];
                    $montantRestant -= $tarif;
                    $moisCouverts++;
                    
                    // On passe au mois suivant
                    $nextMonthToPay->modify('+1 month');
                } else {
                    // Plus assez d'argent pour un mois entier -> on arrête la boucle
                    break;
                }
            }
            // 3. Insertion des opérations générées
            if (!empty($operationsAInserer)) {
                $sql = "INSERT INTO operations (membre_id, categorie_id, montant, type, date_operation, mois_cible, description) VALUES ";
                $placeholders = [];
                $values = [];
                
                foreach ($operationsAInserer as $op) {
                    $placeholders[] = "(?, ?, ?, 'entree', ?, ?, ?)";
                    $values = array_merge($values, [$op['membre_id'], $op['categorie_id'], $op['montant'], $op['date_operation'], $op['mois_cible'], $op['description']]);
                }
                
                $sql .= implode(',', $placeholders);
                $stmt = $this->pdo->prepare($sql);
                $stmt->execute($values);
            }
            // 4. Mise à jour du membre : on avance sa date de début d'arriéré
            $newDateDebut = $nextMonthToPay->format('Y-m-d');
            $stmtUpdateMembre = $this->pdo->prepare("UPDATE membres SET date_debut_arriere = ? WHERE id = ?");
            $stmtUpdateMembre->execute([$newDateDebut, $membreId]);
            // 5. Gestion du reste (Crédit / Avance partielle)
            $creditGenere = $montantRestant;
            if ($creditGenere > 0) {
                // A. Mettre à jour le solde crédit
                $stmtCredit = $this->pdo->prepare("UPDATE membres SET solde_credit = solde_credit + ? WHERE id = ?");
                $stmtCredit->execute([$creditGenere, $membreId]);
                // B. Créer l'opération pour le reste
                $stmtOpCredit = $this->pdo->prepare("
                    INSERT INTO operations (membre_id, categorie_id, montant, type, date_operation, description) 
                    VALUES (?, ?, ?, 'entree', ?, ?)
                ");
                
                $descCredit = "Versement crédit (Reste)";
                if (!empty($description)) {
                    $descCredit .= " - " . $description;
                }
                
                $stmtOpCredit->execute([
                    $membreId, 
                    $this->getCategorieCotisationId(), 
                    $creditGenere, 
                    $dateVersement, 
                    $descCredit
                ]);
            }
            $message = "$moisCouverts mois payé(s) (jusqu'au " . $nextMonthToPay->modify('-1 month')->format('m/Y') . ").";
            if ($creditGenere > 0) {
                $message .= " Reste de " . number_format($creditGenere, 2, ',', ' ') . " FCFA ajouté au crédit.";
            }
            return ["success" => true, "message" => $message];
        } catch (Exception $e) {
            error_log("Erreur dans traiterCotisation: " . $e->getMessage());
            throw $e;
        }
    }
    private function getCategorieCotisationId() {
        try {
            $stmt = $this->pdo->prepare("SELECT id FROM categories WHERE nom = 'Cotisations membres' LIMIT 1");
            $stmt->execute();
            return $stmt->fetchColumn();
        } catch (Exception $e) {
            error_log("Erreur dans getCategorieCotisationId: " . $e->getMessage());
            return null;
        }
    }
}