<?php
require_once 'DatabaseInitializer.php';
require_once "UserManagerDAO.php";

/**
 * Klasse für den Zugriff auf Benutzerdaten über eine SQLite-Datenbank.
 *
 * Diese Klasse verwendet PDO, Prepared Statements und speichert
 * Benutzerdaten in der Datei db/users.db.
 */
class DatabaseUserManager implements UserManagerDAO {

    private static $instance = null;
    private $dbPath;

    /**
     * Konstruktor.
     *
     * Erstellt die Benutzerdatenbank und die Tabelle users,
     * falls diese noch nicht existieren.
     *
     * @throws RuntimeException wenn die Datenbank nicht erstellt werden kann
     */
    public function __construct()
    {
        $this->dbPath = __DIR__ . '/../../db/eduforgeDB.db';
        DatabaseInitializer::initialize($this->dbPath);
    }

    /**
     * Baut eine Verbindung zur SQLite-Datenbank auf.
     *
     * @return PDO Datenbankverbindung
     *
     * @throws RuntimeException wenn keine Verbindung hergestellt werden kann
     */
    private function getConnection()
    {
        try {
            $db = new PDO('sqlite:' . $this->dbPath, null, null);
            $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $db->exec('PRAGMA foreign_keys = ON;');
            return $db;
        } catch (PDOException $e) {
            throw new RuntimeException("internal_error");
        }
    }

    /**
     * Gibt die Singleton-Instanz des DatabaseUserManagers zurück.
     *
     * @return DatabaseUserManager Instanz des DatabaseUserManagers
     */
    public static function getInstance()
    {
        if (self::$instance == null) {
            self::$instance = new DatabaseUserManager();
        }

        return self::$instance;
    }

    /**
     * Sucht einen Benutzer anhand seiner E-Mail-Adresse.
     *
     * @param string $email E-Mail-Adresse des Benutzers
     *
     * @return array|null Benutzerdaten als Array oder null,
     *                    wenn kein Benutzer gefunden wurde
     *
     * @throws RuntimeException wenn der Benutzer nicht geladen werden kann
     */
    public function findUser($email)
    {
        try {
            $db = $this->getConnection();

            $sql = "SELECT * FROM users WHERE email = :email";
            $command = $db->prepare($sql);

            $command->execute([
                ":email" => $email
            ]);

            $user = $command->fetch(PDO::FETCH_ASSOC);

            return $user ?: null;

        } catch (PDOException $e) {
            throw new RuntimeException("Benutzer konnte nicht geladen werden.");
        }
    }

    /**
     * Fügt einen neuen Benutzer in die Datenbank ein.
     *
     * Die Speicherung erfolgt innerhalb einer Transaktion.
     * Doppelte E-Mail-Adressen werden durch den Primary Key verhindert.
     *
     * @param string $email E-Mail-Adresse des Benutzers
     * @param string $vorname Vorname des Benutzers
     * @param string $nachname Nachname des Benutzers
     * @param string $password Passwort-Hash des Benutzers
     *
     * @return void
     *
     * @throws InvalidArgumentException wenn die E-Mail-Adresse bereits verwendet wird
     * @throws RuntimeException wenn der Benutzer nicht gespeichert werden kann
     */
    public function addUser($email, $vorname, $nachname, $password)
    {
        try {
            $db = $this->getConnection();
            $db->beginTransaction();

            $sql = "
                INSERT INTO users (email, vorname, nachname, password)
                VALUES (:email, :vorname, :nachname, :password)
            ";

            $command = $db->prepare($sql);

            $command->execute([
                ":email" => $email,
                ":vorname" => $vorname,
                ":nachname" => $nachname,
                ":password" => $password
            ]);

            $db->commit();

        } catch (PDOException $e) {
            if (isset($db) && $db->inTransaction()) {
                $db->rollBack();
            }

            if ($e->getCode() === "23000") {
                throw new InvalidArgumentException("Diese E-Mail-Adresse wird bereits verwendet.");
            }

            throw new RuntimeException("Benutzer konnte nicht gespeichert werden.");
        }
    }

    /**
     * Aktualisiert die Daten eines bestehenden Benutzers.
     *
     * Optional kann zusätzlich das Passwort geändert werden.
     * Wenn kein neues Passwort übergeben wird, bleibt das alte Passwort erhalten.
     *
     * @param string $oldEmail Aktuelle E-Mail-Adresse des Benutzers
     * @param string $newEmail Neue E-Mail-Adresse des Benutzers
     * @param string $vorname Neuer Vorname des Benutzers
     * @param string $nachname Neuer Nachname des Benutzers
     * @param string|null $password Neues Passwort oder null
     *
     * @return bool true, wenn der Benutzer aktualisiert wurde,
     *              sonst false
     *
     * @throws InvalidArgumentException wenn die neue E-Mail-Adresse bereits verwendet wird
     * @throws RuntimeException wenn der Benutzer nicht aktualisiert werden kann
     */
    public function updateUser($oldEmail, $newEmail, $vorname, $nachname, $password = null)
    {
        try {
            $db = $this->getConnection();

            if (!empty($password)) {
                $sql = "
                    UPDATE users
                    SET email = :newEmail,
                        vorname = :vorname,
                        nachname = :nachname,
                        password = :password
                    WHERE email = :oldEmail
                ";

                $params = [
                    ":newEmail" => $newEmail,
                    ":vorname" => $vorname,
                    ":nachname" => $nachname,
                    ":password" => password_hash($password, PASSWORD_DEFAULT),
                    ":oldEmail" => $oldEmail
                ];
            } else {
                $sql = "
                    UPDATE users
                    SET email = :newEmail,
                        vorname = :vorname,
                        nachname = :nachname
                    WHERE email = :oldEmail
                ";

                $params = [
                    ":newEmail" => $newEmail,
                    ":vorname" => $vorname,
                    ":nachname" => $nachname,
                    ":oldEmail" => $oldEmail
                ];
            }

            $command = $db->prepare($sql);
            $command->execute($params);

            return $command->rowCount() > 0;

        } catch (PDOException $e) {
            if ($e->getCode() === "23000") {
                throw new InvalidArgumentException("Diese E-Mail-Adresse wird bereits verwendet.");
            }

            throw new RuntimeException("Benutzer konnte nicht aktualisiert werden.");
        }
    }

    /**
     * Löscht einen Benutzer anhand seiner E-Mail-Adresse.
     *
     * @param string $email E-Mail-Adresse des zu löschenden Benutzers
     *
     * @return bool true, wenn der Benutzer gelöscht wurde,
     *              sonst false
     *
     * @throws RuntimeException wenn der Benutzer nicht gelöscht werden kann
     */
    public function deleteUser($email)
    {
        try {
            $db = $this->getConnection();

            $sql = "DELETE FROM users WHERE email = :email";
            $command = $db->prepare($sql);

            $command->execute([
                ":email" => $email
            ]);

            return $command->rowCount() > 0;

        } catch (PDOException $e) {
            throw new RuntimeException("Benutzer konnte nicht gelöscht werden.");
        }
    }
}