<?php

/**
 * Хелпер для роботи з запитами до БД
 * Обгортка над QueryBuilder
 *
 * @package Flowaxy\Support\Helpers
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Support\Helpers;

use Flowaxy\Infrastructure\Persistence\Database\QueryBuilder;
use Flowaxy\Infrastructure\Persistence\Database\Database;

final class QueryHelper
{
    /**
     * Створити новий запит
     *
     * @return QueryBuilder
     */
    public static function query(): QueryBuilder
    {
        $db = Database::getInstance();

        return new QueryBuilder($db);
    }

    /**
     * Вибірка з таблиці
     *
     * @param string $table
     * @param string|array<int, string> $columns
     * @return QueryBuilder
     */
    public static function select(string $table, string|array $columns = '*'): QueryBuilder
    {
        return self::query()->from($table)->select($columns);
    }

    /**
     * Вставка в таблицю
     *
     * @param string $table
     * @param array<string, mixed> $values
     * @return bool
     */
    public static function insert(string $table, array $values): bool
    {
        $db = Database::getInstance();
        $columns = array_keys($values);
        $placeholders = array_map(fn($col) => ":{$col}", $columns);
        $sql = "INSERT INTO {$table} (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";

        try {
            $stmt = $db->getConnection()->prepare($sql);
            foreach ($values as $key => $value) {
                $stmt->bindValue(":{$key}", $value);
            }

            return $stmt->execute();
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Оновлення таблиці
     *
     * @param string $table
     * @param array<string, mixed> $values
     * @param array<string, mixed> $where
     * @return bool
     */
    public static function update(string $table, array $values, array $where): bool
    {
        $db = Database::getInstance();
        $set = [];
        foreach (array_keys($values) as $column) {
            $set[] = "{$column} = :set_{$column}";
        }

        $whereClause = [];
        foreach (array_keys($where) as $column) {
            $whereClause[] = "{$column} = :where_{$column}";
        }

        $sql = "UPDATE {$table} SET " . implode(', ', $set) . " WHERE " . implode(' AND ', $whereClause);

        try {
            $stmt = $db->getConnection()->prepare($sql);
            foreach ($values as $key => $value) {
                $stmt->bindValue(":set_{$key}", $value);
            }
            foreach ($where as $key => $value) {
                $stmt->bindValue(":where_{$key}", $value);
            }

            return $stmt->execute();
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Видалення з таблиці
     *
     * @param string $table
     * @param array<string, mixed> $where
     * @return bool
     */
    public static function delete(string $table, array $where): bool
    {
        $db = Database::getInstance();
        $whereClause = [];
        foreach (array_keys($where) as $column) {
            $whereClause[] = "{$column} = :{$column}";
        }

        $sql = "DELETE FROM {$table} WHERE " . implode(' AND ', $whereClause);

        try {
            $stmt = $db->getConnection()->prepare($sql);
            foreach ($where as $key => $value) {
                $stmt->bindValue(":{$key}", $value);
            }

            return $stmt->execute();
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Додати умову WHERE
     *
     * @param QueryBuilder $query
     * @param string $column
     * @param string|null $operator
     * @param mixed $value
     * @return QueryBuilder
     */
    public static function where(QueryBuilder $query, string $column, ?string $operator = null, mixed $value = null): QueryBuilder
    {
        return $query->where($column, $operator, $value);
    }

    /**
     * Додати умову WHERE IN
     *
     * @param QueryBuilder $query
     * @param string $column
     * @param array<int, mixed> $values
     * @return QueryBuilder
     */
    public static function whereIn(QueryBuilder $query, string $column, array $values): QueryBuilder
    {
        return $query->whereIn($column, $values);
    }

    /**
     * Додати умову WHERE NOT IN
     *
     * @param QueryBuilder $query
     * @param string $column
     * @param array<int, mixed> $values
     * @return QueryBuilder
     */
    public static function whereNotIn(QueryBuilder $query, string $column, array $values): QueryBuilder
    {
        return $query->whereNotIn($column, $values);
    }

    /**
     * Додати умову OR WHERE
     *
     * @param QueryBuilder $query
     * @param string $column
     * @param string|null $operator
     * @param mixed $value
     * @return QueryBuilder
     */
    public static function orWhere(QueryBuilder $query, string $column, ?string $operator = null, mixed $value = null): QueryBuilder
    {
        return $query->orWhere($column, $operator, $value);
    }

    /**
     * Додати сортування
     *
     * @param QueryBuilder $query
     * @param string $column
     * @param string $direction
     * @return QueryBuilder
     */
    public static function orderBy(QueryBuilder $query, string $column, string $direction = 'ASC'): QueryBuilder
    {
        return $query->orderBy($column, $direction);
    }

    /**
     * Додати групування
     *
     * @param QueryBuilder $query
     * @param string|array<int, string> $columns
     * @return QueryBuilder
     */
    public static function groupBy(QueryBuilder $query, string|array $columns): QueryBuilder
    {
        return $query->groupBy($columns);
    }

    /**
     * Додати умову HAVING
     *
     * @param QueryBuilder $query
     * @param string $column
     * @param string|null $operator
     * @param mixed $value
     * @return QueryBuilder
     */
    public static function having(QueryBuilder $query, string $column, ?string $operator = null, mixed $value = null): QueryBuilder
    {
        return $query->having($column, $operator, $value);
    }

    /**
     * Обмежити кількість результатів
     *
     * @param QueryBuilder $query
     * @param int $limit
     * @return QueryBuilder
     */
    public static function limit(QueryBuilder $query, int $limit): QueryBuilder
    {
        return $query->limit($limit);
    }

    /**
     * Встановити зміщення
     *
     * @param QueryBuilder $query
     * @param int $offset
     * @return QueryBuilder
     */
    public static function offset(QueryBuilder $query, int $offset): QueryBuilder
    {
        return $query->offset($offset);
    }

    /**
     * Додати JOIN
     *
     * @param QueryBuilder $query
     * @param string $table
     * @param string $first
     * @param string $operator
     * @param string $second
     * @param string $type
     * @return QueryBuilder
     */
    public static function join(QueryBuilder $query, string $table, string $first, string $operator, string $second, string $type = 'INNER'): QueryBuilder
    {
        return $query->join($table, $first, $operator, $second, $type);
    }

    /**
     * Додати LEFT JOIN
     *
     * @param QueryBuilder $query
     * @param string $table
     * @param string $first
     * @param string $operator
     * @param string $second
     * @return QueryBuilder
     */
    public static function leftJoin(QueryBuilder $query, string $table, string $first, string $operator, string $second): QueryBuilder
    {
        return $query->leftJoin($table, $first, $operator, $second);
    }

    /**
     * Додати RIGHT JOIN
     *
     * @param QueryBuilder $query
     * @param string $table
     * @param string $first
     * @param string $operator
     * @param string $second
     * @return QueryBuilder
     */
    public static function rightJoin(QueryBuilder $query, string $table, string $first, string $operator, string $second): QueryBuilder
    {
        return $query->rightJoin($table, $first, $operator, $second);
    }

    /**
     * Додати INNER JOIN
     *
     * @param QueryBuilder $query
     * @param string $table
     * @param string $first
     * @param string $operator
     * @param string $second
     * @return QueryBuilder
     */
    public static function innerJoin(QueryBuilder $query, string $table, string $first, string $operator, string $second): QueryBuilder
    {
        return $query->join($table, $first, $operator, $second, 'INNER');
    }

    /**
     * Підрахувати кількість записів
     *
     * @param QueryBuilder $query
     * @return int
     */
    public static function count(QueryBuilder $query): int
    {
        return $query->count();
    }

    /**
     * Перевірити існування записів
     *
     * @param QueryBuilder $query
     * @return bool
     */
    public static function exists(QueryBuilder $query): bool
    {
        return $query->exists();
    }

    /**
     * Отримати перший запис
     *
     * @param QueryBuilder $query
     * @return array<string, mixed>|false
     */
    public static function first(QueryBuilder $query): array|false
    {
        return $query->first();
    }

    /**
     * Отримати всі записи
     *
     * @param QueryBuilder $query
     * @return array<int, array<string, mixed>>
     */
    public static function get(QueryBuilder $query): array
    {
        return $query->get();
    }

    /**
     * Отримати записи з пагінацією
     *
     * @param QueryBuilder $query
     * @param int $perPage
     * @param int $page
     * @return array{data: array<int, array<string, mixed>>, total: int, per_page: int, current_page: int, last_page: int}
     */
    public static function paginate(QueryBuilder $query, int $perPage = 15, int $page = 1): array
    {
        $total = $query->count();
        $lastPage = (int)ceil($total / $perPage);
        $offset = ($page - 1) * $perPage;

        $data = $query->limit($perPage)->offset($offset)->get();

        return [
            'data' => $data,
            'total' => $total,
            'per_page' => $perPage,
            'current_page' => $page,
            'last_page' => $lastPage,
        ];
    }

    /**
     * Поиск записи по slug
     *
     * @param string $table Таблица
     * @param string $slug Slug
     * @return array<string, mixed>|false
     */
    public static function findBySlug(string $table, string $slug): array|false
    {
        $query = self::query()
            ->from($table)
            ->where('slug', '=', $slug)
            ->limit(1);

        return $query->first();
    }

    /**
     * Поиск активных записей
     *
     * @param string $table Таблица
     * @param string $statusColumn Название колонки статуса (по умолчанию 'is_active')
     * @return array<int, array<string, mixed>>
     */
    public static function findActive(string $table, string $statusColumn = 'is_active'): array
    {
        $query = self::query()
            ->from($table)
            ->where($statusColumn, '=', 1);

        return $query->get();
    }

    /**
     * Обновление статуса записи
     *
     * @param string $table Таблица
     * @param string $slug Slug записи
     * @param int|bool $status Новый статус
     * @param string $statusColumn Название колонки статуса (по умолчанию 'is_active')
     * @return bool
     */
    public static function updateStatus(string $table, string $slug, int|bool $status, string $statusColumn = 'is_active'): bool
    {
        $statusValue = is_bool($status) ? ($status ? 1 : 0) : $status;

        return self::update($table, [$statusColumn => $statusValue], ['slug' => $slug]);
    }

    /**
     * Удаление записи по slug
     *
     * @param string $table Таблица
     * @param string $slug Slug
     * @return bool
     */
    public static function deleteBySlug(string $table, string $slug): bool
    {
        return self::delete($table, ['slug' => $slug]);
    }

    /**
     * Проверка существования записи по slug
     *
     * @param string $table Таблица
     * @param string $slug Slug
     * @return bool
     */
    public static function existsBySlug(string $table, string $slug): bool
    {
        $query = self::query()
            ->from($table)
            ->where('slug', '=', $slug)
            ->limit(1);

        return $query->exists();
    }
}
