<?php

declare(strict_types=1);

namespace App\Models;

use App\Core\Database;
use PDO;

abstract class BaseModel
{
    protected string $table;
    protected string $primaryKey = 'id';
    protected ?string $tenantColumn = 'empresa_id';

    protected function db(): PDO
    {
        return Database::connection();
    }

    public function all(array $conditions = [], ?int $tenantId = null, string $orderBy = 'id DESC'): array
    {
        $bindings = [];
        $where = $this->buildWhere($conditions, $bindings, $tenantId);
        $sql = sprintf('SELECT * FROM %s %s ORDER BY %s', $this->table, $where, $orderBy);
        $stmt = $this->db()->prepare($sql);
        $stmt->execute($bindings);
        return $stmt->fetchAll() ?: [];
    }

    public function findById(int $id, ?int $tenantId = null): ?array
    {
        $bindings = [];
        $where = $this->buildWhere([$this->primaryKey => $id], $bindings, $tenantId);

        $sql = sprintf('SELECT * FROM %s %s LIMIT 1', $this->table, $where);
        $stmt = $this->db()->prepare($sql);
        $stmt->execute($bindings);
        $result = $stmt->fetch();

        return $result ?: null;
    }

    public function create(array $data): int
    {
        $columns = array_keys($data);
        $placeholders = array_map(static fn (string $column): string => ':' . $column, $columns);

        $sql = sprintf(
            'INSERT INTO %s (%s) VALUES (%s)',
            $this->table,
            implode(', ', $columns),
            implode(', ', $placeholders)
        );

        $stmt = $this->db()->prepare($sql);
        $stmt->execute($data);

        return (int) $this->db()->lastInsertId();
    }

    public function updateById(int $id, array $data, ?int $tenantId = null): bool
    {
        if ($data === []) {
            return false;
        }

        $setClauses = [];
        foreach ($data as $column => $value) {
            $setClauses[] = $column . ' = :' . $column;
        }

        $bindings = $data;
        $where = $this->buildWhere([$this->primaryKey => $id], $bindings, $tenantId);

        $sql = sprintf(
            'UPDATE %s SET %s %s',
            $this->table,
            implode(', ', $setClauses),
            $where
        );

        $stmt = $this->db()->prepare($sql);
        return $stmt->execute($bindings);
    }

    public function deleteById(int $id, ?int $tenantId = null): bool
    {
        $bindings = [];
        $where = $this->buildWhere([$this->primaryKey => $id], $bindings, $tenantId);

        $sql = sprintf('DELETE FROM %s %s', $this->table, $where);
        $stmt = $this->db()->prepare($sql);
        return $stmt->execute($bindings);
    }

    protected function buildWhere(array $conditions, array &$bindings, ?int $tenantId): string
    {
        $where = [];

        foreach ($conditions as $column => $value) {
            $param = 'w_' . $column;
            if (array_key_exists($param, $bindings)) {
                $param .= '_' . count($where);
            }

            $where[] = $column . ' = :' . $param;
            $bindings[$param] = $value;
        }

        if ($tenantId !== null && $this->tenantColumn !== null) {
            $where[] = $this->tenantColumn . ' = :tenant_id';
            $bindings['tenant_id'] = $tenantId;
        }

        if ($where === []) {
            return '';
        }

        return 'WHERE ' . implode(' AND ', $where);
    }
}
