<?php

namespace App\Models;

use App\Core\Database;

/**
 * Base database model implementing generic active-record CRUD routines.
 */
abstract class BaseModel
{
    protected static string $table = '';
    protected static string $primaryKey = 'id';

    /**
     * Validate table, primary key, and column names to prevent SQL injection.
     */
    protected static function getSafeTable(): string
    {
        $table = static::$table;
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
            throw new \InvalidArgumentException("Invalid table name: {$table}");
        }
        return $table;
    }

    protected static function getSafePrimaryKey(): string
    {
        $primaryKey = static::$primaryKey;
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $primaryKey)) {
            throw new \InvalidArgumentException("Invalid primary key: {$primaryKey}");
        }
        return $primaryKey;
    }

    protected static function validateColumns(array $columns): void
    {
        foreach ($columns as $column) {
            if (!preg_match('/^[a-zA-Z0-9_]+$/', $column)) {
                throw new \InvalidArgumentException("Invalid column name: {$column}");
            }
        }
    }

    /**
     * Retrieve all records
     */
    public static function all(): array
    {
        $table = static::getSafeTable();
        return Database::select("SELECT * FROM {$table}");
    }

    /**
     * Find a record by its primary key
     *
     * @param mixed $id
     * @return array|null
     */
    public static function find($id): ?array
    {
        // Enforce integer conversion or validate to prevent type misuse
        $id = is_numeric($id) ? (int)$id : $id;
        $table = static::getSafeTable();
        $primaryKey = static::getSafePrimaryKey();
        return Database::selectOne("SELECT * FROM {$table} WHERE {$primaryKey} = :id LIMIT 1", ['id' => $id]);
    }

    /**
     * Insert a new record
     *
     * @param array $data Association array of columns and values
     * @return string Last inserted primary key ID
     */
    public static function create(array $data): string
    {
        $table = static::getSafeTable();
        $columnKeys = array_keys($data);
        static::validateColumns($columnKeys);

        $columns = implode(', ', $columnKeys);
        $placeholders = ':' . implode(', :', $columnKeys);

        $sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";
        return Database::insert($sql, $data);
    }

    /**
     * Update an existing record
     *
     * @param mixed $id Primary key ID
     * @param array $data Associative array of changes
     * @return int Affected rows count
     */
    public static function update($id, array $data): int
    {
        $id = is_numeric($id) ? (int)$id : $id;
        $table = static::getSafeTable();
        $primaryKey = static::getSafePrimaryKey();
        
        $columnKeys = array_keys($data);
        static::validateColumns($columnKeys);

        $sets = [];
        foreach ($columnKeys as $column) {
            $sets[] = "{$column} = :{$column}";
        }
        $setSql = implode(', ', $sets);

        $sql = "UPDATE {$table} SET {$setSql} WHERE {$primaryKey} = :_id";
        
        $params = $data;
        $params['_id'] = $id;

        return Database::execute($sql, $params);
    }

    /**
     * Delete record by ID
     */
    public static function delete($id): int
    {
        $id = is_numeric($id) ? (int)$id : $id;
        $table = static::getSafeTable();
        $primaryKey = static::getSafePrimaryKey();

        $sql = "DELETE FROM {$table} WHERE {$primaryKey} = :id";
        return Database::execute($sql, ['id' => $id]);
    }
}
