<?php

/**
 * Абстрактный базовый класс для репозиториев
 *
 * @package Flowaxy\Infrastructure\Persistence\Repositories
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Persistence\Repositories;

use Flowaxy\Infrastructure\Persistence\Database\Database;
use Flowaxy\Support\Helpers\DatabaseHelper;
use Flowaxy\Support\Helpers\ExceptionHandler;
use Flowaxy\Support\Helpers\SafeLogger;
use PDO;
use PDOException;
use Throwable;

use function array_map;

abstract class AbstractRepository implements RepositoryInterface
{
    protected ?PDO $connection = null;
    protected string $tableName;

    public function __construct()
    {
        $this->connection = ExceptionHandler::safeExecute(
            fn() => DatabaseHelper::getConnection(),
            null,
            'Помилка підключення до бази даних в репозиторії',
            ['repository' => static::class]
        );
    }

    /**
     * Получить все записи
     *
     * @return array<int, object>
     */
    public function all(): array
    {
        return ExceptionHandler::safeExecute(
            function () {
                $rows = DatabaseHelper::fetchAll("SELECT * FROM {$this->tableName}");
                return array_map([$this, 'mapToEntity'], $rows);
            },
            [],
            'Помилка отримання всіх записів',
            ['table' => $this->tableName, 'repository' => static::class]
        );
    }

    /**
     * Найти запись по идентификатору
     *
     * @param string|int $id
     * @return object|null
     */
    public function find($id): ?object
    {
        return ExceptionHandler::safeExecute(
            function () use ($id) {
                $row = DatabaseHelper::fetchOne("SELECT * FROM {$this->tableName} WHERE id = ?", [$id]);
                return $row ? $this->mapToEntity($row) : null;
            },
            null,
            'Помилка пошуку запису',
            ['table' => $this->tableName, 'id' => $id, 'repository' => static::class]
        );
    }

    /**
     * Сохранить запись
     *
     * @param object $entity
     * @return bool
     */
    public function save(object $entity): bool
    {
        return ExceptionHandler::safeExecute(
            function () use ($entity) {
                $data = $this->mapFromEntity($entity);
                $id = $data['id'] ?? null;

                if ($id !== null && $this->find($id) !== null) {
                    return $this->update($data);
                } else {
                    return $this->insert($data);
                }
            },
            false,
            'Помилка збереження запису',
            ['table' => $this->tableName, 'repository' => static::class]
        );
    }

    /**
     * Удалить запись
     *
     * @param string|int $id
     * @return bool
     */
    public function delete($id): bool
    {
        return ExceptionHandler::safeExecute(
            function () use ($id) {
                $stmt = DatabaseHelper::query("DELETE FROM {$this->tableName} WHERE id = ?", [$id]);
                return $stmt !== false;
            },
            false,
            'Помилка видалення запису',
            ['table' => $this->tableName, 'id' => $id, 'repository' => static::class]
        );
    }

    /**
     * Вставить новую запись
     *
     * @param array<string, mixed> $data
     * @return bool
     */
    protected function insert(array $data): bool
    {
        unset($data['id']);
        $columns = array_keys($data);
        $placeholders = array_fill(0, count($columns), '?');

        $sql = "INSERT INTO {$this->tableName} (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
        $stmt = DatabaseHelper::query($sql, array_values($data));
        return $stmt !== false;
    }

    /**
     * Обновить существующую запись
     *
     * @param array<string, mixed> $data
     * @return bool
     */
    protected function update(array $data): bool
    {
        $id = $data['id'];
        unset($data['id']);

        $columns = array_keys($data);
        $set = implode(', ', array_map(fn($col) => "{$col} = ?", $columns));

        $sql = "UPDATE {$this->tableName} SET {$set} WHERE id = ?";
        $stmt = DatabaseHelper::query($sql, [...array_values($data), $id]);
        return $stmt !== false;
    }

    /**
     * Преобразовать строку БД в сущность
     *
     * @param array<string, mixed> $row
     * @return object
     */
    abstract protected function mapToEntity(array $row): object;

    /**
     * Преобразовать сущность в массив для БД
     *
     * @param object $entity
     * @return array<string, mixed>
     */
    abstract protected function mapFromEntity(object $entity): array;
}
