<?php
/**
 * HuSNS - 一款免费开源的社交平台
 * 
 * @author  HYR
 * @QQ      281900864
 * @website https://huyourui.com
 * @license MIT
 * @声明    严禁用于违法违规用途
 */
class Model
{
    protected $db;
    protected $table;
    protected $primaryKey = 'id';
    
    /**
     * 允许排序的字段白名单，用于防止 SQL 注入
     * 子类可覆盖此属性
     */
    protected $allowedOrderColumns = [];

    public function __construct()
    {
        $this->db = Database::getInstance();
    }

    public function find($id)
    {
        $table = $this->db->table($this->table);
        return $this->db->fetch("SELECT * FROM `{$table}` WHERE `{$this->primaryKey}` = ?", [$id]);
    }

    /**
     * 安全验证 ORDER BY 子句 - 防止 SQL 注入
     */
    private function sanitizeOrderBy(string $orderBy): string
    {
        // 清理空格
        $orderBy = trim($orderBy);
        
        // 检查是否包含非法字符（分号、引号、反斜杠等）
        if (preg_match('/[;\'\"`\\\]/', $orderBy)) {
            return '';
        }
        
        // 解析字段名和排序方向
        $parts = array_map('trim', explode(',', $orderBy));
        $safeParts = [];
        
        foreach ($parts as $part) {
            // 支持 field ASC/DESC 格式
            if (preg_match('/^(\w+)\s+(ASC|DESC)$/i', $part, $matches)) {
                $column = $matches[1];
                $direction = strtoupper($matches[2]);
                
                // 如果设置了白名单，只允许白名单中的字段
                if (!empty($this->allowedOrderColumns) && !in_array($column, $this->allowedOrderColumns)) {
                    continue;
                }
                
                $safeParts[] = "`{$column}` {$direction}";
            } 
            // 也支持只有字段名的情况，默认升序
            elseif (preg_match('/^\w+$/', $part)) {
                if (!empty($this->allowedOrderColumns) && !in_array($part, $this->allowedOrderColumns)) {
                    continue;
                }
                $safeParts[] = "`{$part}` ASC";
            }
        }
        
        return implode(', ', $safeParts);
    }

    /**
     * 安全验证 LIMIT 子句 - 仅允许数字和逗号
     */
    private function sanitizeLimit(string $limit): string
    {
        // 移除所有非数字和逗号的字符
        $limit = preg_replace('/[^0-9,\s]/', '', $limit);
        $limit = trim($limit);
        
        // 验证格式：offset,count 或 count
        if (preg_match('/^\d+$/', $limit)) {
            return $limit;
        }
        if (preg_match('/^\d+\s*,\s*\d+$/', $limit)) {
            return preg_replace('/\s+/', '', $limit);
        }
        
        return '';
    }

    public function findAll($where = '1', $params = [], $orderBy = '', $limit = '')
    {
        $table = $this->db->table($this->table);
        $sql = "SELECT * FROM `{$table}` WHERE {$where}";
        
        // 安全验证 orderBy - 使用白名单
        if ($orderBy) {
            $safeOrderBy = $this->sanitizeOrderBy($orderBy);
            if ($safeOrderBy) {
                $sql .= " ORDER BY {$safeOrderBy}";
            }
        }
        
        // 安全验证 limit - 仅允许数字和逗号
        if ($limit) {
            $safeLimit = $this->sanitizeLimit($limit);
            if ($safeLimit) {
                $sql .= " LIMIT {$safeLimit}";
            }
        }
        
        return $this->db->fetchAll($sql, $params);
    }

    public function insert($data)
    {
        $data = $this->beforeInsert($data);
        $result = $this->db->insert($this->table, $data);
        return $this->afterInsert($result);
    }

    public function update($id, $data)
    {
        $data = $this->beforeUpdate($data);
        return $this->db->update($this->table, $data, "`{$this->primaryKey}` = ?", [$id]);
    }

    public function delete($id)
    {
        $this->beforeDelete($id);
        return $this->db->delete($this->table, "`{$this->primaryKey}` = ?", [$id]);
    }

    public function count($where = '1', $params = [])
    {
        return $this->db->count($this->table, $where, $params);
    }

    public function paginate($page = 1, $pageSize = 10, $where = '1', $params = [], $orderBy = '')
    {
        $total = $this->count($where, $params);
        $totalPages = ceil($total / $pageSize);
        $page = max(1, min($page, $totalPages));
        $offset = ($page - 1) * $pageSize;
        
        $limit = "{$offset}, {$pageSize}";
        $items = $this->findAll($where, $params, $orderBy, $limit);

        return [
            'items' => $items,
            'total' => $total,
            'page' => $page,
            'pageSize' => $pageSize,
            'totalPages' => $totalPages
        ];
    }

    protected function beforeInsert($data)
    {
        return $data;
    }

    protected function afterInsert($id)
    {
        return $id;
    }

    protected function beforeUpdate($data)
    {
        return $data;
    }

    protected function beforeDelete($id)
    {
        return true;
    }
}
