<?php

namespace App\Repositories;

use App\Config\Database;

abstract class BaseRepository
{

    abstract protected function getAllowedFields(): array;

    /**
     * Get all from table by params
     * 
     * @throws Exception
     * 
     * @return array
     * 
     * @param string $table
     * @param array $params
     */
    protected function getAllFromTable(string $table, array $params): array
    {
        $allowedFields = $this->getAllowedFields();

        // -------- SELECT --------
        $fields = '*';
        if (!empty($params['fields'])) {
            $fields = implode(', ', array_map(
                fn($field) => $this->sanitizeField($field, $allowedFields),
                $params['fields']
            ));
        }

        $sql = "SELECT $fields FROM {$table}";

        // -------- FILTERS --------
        $where  = [];
        $types  = '';
        $values = [];

        if (!empty($params['filterBy'])) {
            foreach ($params['filterBy'] as $filter) {
                $field = $this->sanitizeField($filter['field'], $allowedFields);
                $op    = $this->sanitizeOperator($filter['operator']);

                $where[] = "$field $op ?";
                $types  .= is_int($filter['value']) ? 'i' : 's';
                $values[] = $filter['value'];
            }
        }

        if ($where) {
            $sql .= ' WHERE ' . implode(' AND ', $where);
        }

        // -------- ORDER BY --------
        if (!empty($params['orderBy'])) {
            $orders = [];
            foreach ($params['orderBy'] as $order) {
                $field = $this->sanitizeField($order['field'], $allowedFields);
                $dir   = strtoupper($order['direction']) === 'DESC' ? 'DESC' : 'ASC';
                $orders[] = "$field $dir";
            }
            $sql .= ' ORDER BY ' . implode(', ', $orders);
        }

        // -------- PAGINATION --------
        $sql .= ' LIMIT ? OFFSET ?';
        $types  .= 'ii';
        $values[] = (int)$params['options']['limit'];
        $values[] = (int)$params['options']['offset'];

        // -------- EXEC --------
        $conn = Database::connection();
        $stmt = $conn->prepare($sql);
        $stmt->bind_param($types, ...$values);
        $stmt->execute();

        return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
    }

    // ================= HELPERS =================

    /**
     * Sanitize field
     * 
     * @throws Exception
     * 
     * @return string
     * 
     * @param string $field
     * @param array $allowedFields
     */
    protected function sanitizeField(string $field, array $allowedFields): string
    {
        if (!isset($allowedFields[$field])) {
            throw new \InvalidArgumentException("INVALID_FIELD: {$field}");
        }

        return $allowedFields[$field];
    }

    /**
     * Sanitize operator
     * 
     * @throws Exception
     * 
     * @return string
     * 
     * @param string $op
     */
    protected function sanitizeOperator(string $op): string
    {
        $allowed = ['=', '!=', '>', '<', '>=', '<='];
        if (!in_array($op, $allowed, true)) {
            throw new \InvalidArgumentException("INVALID_OPERATOR");
        }
        return $op;
    }
}
