<?php
/**
 * Mini-projet 06 — Système Bancaire OOP
 * Démarrage : php -S localhost:8000 puis http://localhost:8000/solution.php
 */

// ── Interface ──────────────────────────────────────────────
interface Transactionnable {
    public function crediter(float $montant, string $libelle = ''): void;
    public function debiter(float $montant, string $libelle = ''): void;
    public function getSolde(): float;
}

// ── Trait de journalisation ────────────────────────────────
trait JournalTransactions {
    private array $journal = [];

    protected function journaliser(string $type, float $montant, float $soldeApres, string $libelle = ''): void {
        $this->journal[] = [
            'date'     => date('Y-m-d H:i:s'),
            'type'     => $type,
            'montant'  => $montant,
            'solde'    => $soldeApres,
            'libelle'  => $libelle,
        ];
    }

    public function getJournal(): array { return $this->journal; }
}

// ── Classe abstraite ───────────────────────────────────────
abstract class Compte implements Transactionnable {
    use JournalTransactions;

    protected float $solde;
    private static int $compteur = 0;
    public readonly string $numero;

    public function __construct(
        public readonly string $titulaire,
        float $soldeInitial = 0.0
    ) {
        // Génération automatique du numéro de compte
        self::$compteur++;
        $this->numero = 'FR' . str_pad((string)self::$compteur, 8, '0', STR_PAD_LEFT);
        $this->solde  = $soldeInitial;
        $this->journaliser('OUVERTURE', $soldeInitial, $soldeInitial, 'Ouverture du compte');
    }

    public function getSolde(): float { return $this->solde; }

    // Méthode concrète commune aux deux types de comptes
    public function crediter(float $montant, string $libelle = 'Crédit'): void {
        if ($montant <= 0) throw new InvalidArgumentException("Montant doit être positif");
        $this->solde += $montant;
        $this->journaliser('CREDIT', $montant, $this->solde, $libelle);
    }

    // Méthode abstraite : chaque type gère le découvert différemment
    abstract public function debiter(float $montant, string $libelle = 'Débit'): void;

    public function __toString(): string {
        return sprintf('[%s] %s — %.2f €', $this->numero, $this->titulaire, $this->solde);
    }
}

// ── Compte Courant ─────────────────────────────────────────
class CompteCourant extends Compte {
    public function __construct(
        string $titulaire,
        float  $soldeInitial   = 0.0,
        private float $decouvertMax = 500.0  // montant maximum du découvert autorisé
    ) {
        parent::__construct($titulaire, $soldeInitial);
    }

    public function debiter(float $montant, string $libelle = 'Débit'): void {
        if ($montant <= 0) throw new InvalidArgumentException("Montant doit être positif");
        // Vérification du découvert autorisé
        if ($this->solde - $montant < -$this->decouvertMax) {
            throw new OverflowException("Découvert maximum ({$this->decouvertMax} €) dépassé");
        }
        // Frais de découvert : 5% sur la partie négative générée
        $frais = 0;
        if ($this->solde - $montant < 0) {
            $frais = abs(min(0, $this->solde - $montant)) * 0.05;
        }
        $this->solde -= $montant + $frais;
        $this->journaliser('DEBIT', $montant + $frais, $this->solde,
            $libelle . ($frais > 0 ? sprintf(' (+%.2f€ frais)', $frais) : ''));
    }

    public function getDecouvertMax(): float { return $this->decouvertMax; }
}

// ── Compte Épargne ─────────────────────────────────────────
class CompteEpargne extends Compte {
    public function __construct(
        string $titulaire,
        float  $soldeInitial  = 0.0,
        private float $tauxAnnuel = 3.0   // taux d'intérêt annuel en %
    ) {
        parent::__construct($titulaire, $soldeInitial);
    }

    // Pas de découvert possible sur un livret d'épargne
    public function debiter(float $montant, string $libelle = 'Retrait'): void {
        if ($montant <= 0) throw new InvalidArgumentException("Montant doit être positif");
        if ($montant > $this->solde) {
            throw new UnderflowException("Solde insuffisant ({$this->solde} €)");
        }
        $this->solde -= $montant;
        $this->journaliser('DEBIT', $montant, $this->solde, $libelle);
    }

    // Applique un mois d'intérêts (taux annuel / 12)
    public function appliquerInteretsMensuels(): float {
        $interets = $this->solde * ($this->tauxAnnuel / 100 / 12);
        $this->solde += $interets;
        $this->journaliser('INTERETS', $interets, $this->solde,
            sprintf('Intérêts %.2f%% / 12', $this->tauxAnnuel));
        return $interets;
    }
}

// ── Virement ───────────────────────────────────────────────
class Virement {
    public static function effectuer(Compte $source, Compte $dest, float $montant, string $ref = ''): void {
        $libelle = $ref ?: "Virement {$source->numero} → {$dest->numero}";
        // Atomicité simulée : si le débit échoue, on ne crédite pas
        $source->debiter($montant, "VIR OUT $libelle");
        $dest->crediter($montant, "VIR IN $libelle");
    }
}

// ── Banque Singleton ───────────────────────────────────────
class Banque {
    private static ?Banque $instance = null;
    private array $comptes = [];
    public readonly string $nom;

    private function __construct(string $nom) { $this->nom = $nom; }

    public static function getInstance(string $nom = 'PHP Banque'): static {
        if (static::$instance === null) {
            static::$instance = new static($nom);
        }
        return static::$instance;
    }

    public function ouvrirCompte(Compte $c): void { $this->comptes[$c->numero] = $c; }
    public function getCompte(string $numero): ?Compte { return $this->comptes[$numero] ?? null; }
    public function getComptes(): array { return $this->comptes; }

    public function totalDepots(): float {
        // array_sum + array_map : calcul du bilan total de la banque
        return array_sum(array_map(fn($c) => max(0, $c->getSolde()), $this->comptes));
    }
}

// ─────────────────────────────────────────────────────────────
// ── Démonstration ─────────────────────────────────────────
// ─────────────────────────────────────────────────────────────
$banque = Banque::getInstance('FormaPHP Bank');
$cc  = new CompteCourant('Alice Martin', 1000.0, 500.0);
$ce  = new CompteEpargne('Alice Martin', 5000.0, 2.5);
$cc2 = new CompteCourant('Bob Dupont', 200.0);
$banque->ouvrirCompte($cc);
$banque->ouvrirCompte($ce);
$banque->ouvrirCompte($cc2);

$cc->crediter(500, 'Salaire');
$cc->debiter(150, 'Loyer');

try {
    Virement::effectuer($cc, $cc2, 200, 'Remboursement');
    $cc->debiter(1400, 'Test découvert'); // dépassera le découvert
} catch (OverflowException $e) {
    // pas de gestion silencieuse : on affiche l'exception pour le débogage
}

$interets = $ce->appliquerInteretsMensuels();

?><!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Système Bancaire OOP</title>
  <style>
    *{margin:0;padding:0;box-sizing:border-box}
    body{background:#0a0e1a;color:#e6edf3;font-family:'Segoe UI',sans-serif;padding:2rem 1rem}
    .container{max-width:860px;margin:0 auto}
    h1{font-size:1.6rem;font-weight:800;background:linear-gradient(135deg,#8892be,#b0b9e8);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:.3rem}
    h2{font-size:1rem;font-weight:700;color:#8892be;margin:1.5rem 0 .7rem}
    .bank-header{margin-bottom:2rem}
    .bank-header p{color:#6e7681;font-size:.85rem}
    .accounts{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1rem;margin-bottom:1.5rem}
    .account{background:rgba(22,27,34,.8);border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:1.2rem}
    .account-type{font-size:.72rem;text-transform:uppercase;letter-spacing:1px;color:#8b949e;margin-bottom:.3rem}
    .account-num{font-size:.78rem;color:#6e7681;margin-bottom:.5rem}
    .account-name{font-weight:700;font-size:1rem;margin-bottom:.5rem}
    .account-solde{font-size:1.4rem;font-weight:800}
    .solde-pos{color:#3fb950}.solde-neg{color:#f85149}
    .journal{background:rgba(22,27,34,.6);border:1px solid rgba(255,255,255,.06);border-radius:8px;padding:1rem;margin-top:.8rem;font-size:.78rem}
    .tx{display:flex;justify-content:space-between;padding:.25rem 0;border-bottom:1px solid rgba(255,255,255,.04)}
    .tx:last-child{border-bottom:none}
    .tx-credit{color:#3fb950}.tx-debit{color:#f85149}.tx-other{color:#8892be}
    .bilan{background:rgba(136,146,190,.06);border:1px solid rgba(136,146,190,.2);border-radius:10px;padding:1rem 1.2rem;margin-top:1.5rem}
    .bilan h2{margin-top:0}
  </style>
</head>
<body>
<div class="container">
  <div class="bank-header">
    <h1>🏦 <?= htmlspecialchars($banque->nom) ?></h1>
    <p>Système bancaire en PHP Orienté Objet — Démonstration</p>
  </div>

  <h2>Comptes ouverts</h2>
  <div class="accounts">
    <?php foreach ($banque->getComptes() as $compte): ?>
      <?php $type = $compte instanceof CompteEpargne ? 'Compte Épargne' : 'Compte Courant'; ?>
      <div class="account">
        <div class="account-type"><?= $type ?></div>
        <div class="account-num"><?= htmlspecialchars($compte->numero) ?></div>
        <div class="account-name"><?= htmlspecialchars($compte->titulaire) ?></div>
        <div class="account-solde <?= $compte->getSolde() >= 0 ? 'solde-pos' : 'solde-neg' ?>">
          <?= number_format($compte->getSolde(), 2, ',', ' ') ?> €
        </div>
        <?php if ($compte instanceof CompteEpargne): ?>
          <div style="font-size:.75rem;color:#8b949e;margin-top:.3rem">Intérêts ce mois : +<?= number_format($interets, 4) ?> €</div>
        <?php endif; ?>
        <div class="journal">
          <?php foreach (array_slice($compte->getJournal(), -5) as $tx): ?>
            <?php
              $cls = match($tx['type']) {
                'CREDIT', 'INTERETS' => 'tx-credit',
                'DEBIT'              => 'tx-debit',
                default              => 'tx-other',
              };
            ?>
            <div class="tx">
              <span class="<?= $cls ?>"><?= htmlspecialchars($tx['type']) ?> — <?= htmlspecialchars($tx['libelle']) ?></span>
              <span><?= number_format($tx['montant'], 2) ?> €</span>
            </div>
          <?php endforeach; ?>
        </div>
      </div>
    <?php endforeach; ?>
  </div>

  <div class="bilan">
    <h2>📊 Bilan <?= htmlspecialchars($banque->nom) ?></h2>
    <p style="color:#8b949e;font-size:.88rem">Nombre de comptes : <strong style="color:#e6edf3"><?= count($banque->getComptes()) ?></strong></p>
    <p style="color:#8b949e;font-size:.88rem">Total des dépôts : <strong style="color:#3fb950"><?= number_format($banque->totalDepots(), 2, ',', ' ') ?> €</strong></p>
  </div>
</div>
</body>
</html>
