<?php

declare(strict_types=1);

namespace Fennec\Database;

/**
 * Wrapper PDO : connexion paresseuse, requêtes systématiquement préparées,
 * exceptions PDO traduites en exceptions applicatives.
 *
 * Ne fournit ni query builder ni ORM — le pattern Repository reste une
 * convention côté App\, pas un composant du framework.
 */
final class Database
{
    private ?\PDO $connection = null;

    public function __construct(
        private readonly string $dsn,
        private readonly string $username,
        private readonly string $password,
    ) {
    }

    /**
     * @param array<int|string, mixed> $params
     *
     * @return list<array<string, mixed>>
     *
     * @throws QueryException
     */
    public function fetchAll(string $sql, array $params = []): array
    {
        return $this->run($sql, $params)->fetchAll();
    }

    /**
     * @param array<int|string, mixed> $params
     *
     * @return array<string, mixed>|null
     *
     * @throws QueryException
     */
    public function fetchOne(string $sql, array $params = []): ?array
    {
        $row = $this->run($sql, $params)->fetch();

        return $row === false ? null : $row;
    }

    /**
     * Exécute une requête de modification (INSERT/UPDATE/DELETE).
     *
     * @param array<int|string, mixed> $params
     *
     * @return int Nombre de lignes affectées.
     *
     * @throws QueryException
     */
    public function execute(string $sql, array $params = []): int
    {
        return $this->run($sql, $params)->rowCount();
    }

    /**
     * @throws ConnectionException
     */
    public function lastInsertId(): string
    {
        return $this->connect()->lastInsertId();
    }

    /**
     * @param array<int|string, mixed> $params
     *
     * @throws ConnectionException
     * @throws QueryException
     */
    private function run(string $sql, array $params): \PDOStatement
    {
        try {
            $statement = $this->connect()->prepare($sql);
            $statement->execute($params);

            return $statement;
        } catch (\PDOException $e) {
            throw new QueryException($sql, $e->getMessage());
        }
    }

    /**
     * @throws ConnectionException
     */
    private function connect(): \PDO
    {
        if ($this->connection === null) {
            try {
                $this->connection = new \PDO($this->dsn, $this->username, $this->password, [
                    \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                    \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
                    \PDO::ATTR_EMULATE_PREPARES => false,
                ]);
            } catch (\PDOException $e) {
                throw new ConnectionException($e->getMessage());
            }
        }

        return $this->connection;
    }
}
