<?php

namespace App\Core;

use App\Core\Database;

abstract class Model
{
    protected $table;
    protected $primaryKey = 'id';
    protected $attributes = [];
    protected $relations = [];
    protected static $queryParts = [];

    public function __construct($attributes = [])
    {
        $this->attributes = $attributes;
    }

    public function __get($key)
    {
        if (array_key_exists($key, $this->attributes)) {
            return $this->attributes[$key];
        }

        // Check for relationship method
        if (method_exists($this, $key)) {
            $relationPromise = $this->$key();
            // In a real ORM we would cache this
            return $relationPromise;
        }

        return null;
    }

    public function __set($key, $value)
    {
        $this->attributes[$key] = $value;
    }

    public static function query()
    {
        $instance = new static;
        $instance::$queryParts = ['where' => [], 'params' => [], 'orderBy' => ''];
        return $instance;
    }

    public static function where($column, $operator = null, $value = null)
    {
        $instance = new static;
        if (func_num_args() == 2) {
            $value = $operator;
            $operator = '=';
        }
        $instance::$queryParts['where'][] = "{$column} {$operator} ?";
        $instance::$queryParts['params'][] = $value;
        return $instance;
    }

    // To support chaining where on an instance
    public function andWhere($column, $operator = null, $value = null)
    {
        if (func_num_args() == 2) {
            $value = $operator;
            $operator = '=';
        }
        static::$queryParts['where'][] = "{$column} {$operator} ?";
        static::$queryParts['params'][] = $value;
        return $this;
    }

    public static function create($data)
    {
        $instance = new static($data);
        $instance->save();
        return $instance;
    }

    public static function find($id)
    {
        $instance = new static;
        $currTable = $instance->table;
        return $instance->fetchOne("SELECT * FROM {$currTable} WHERE {$instance->primaryKey} = ?", [$id]);
    }

    public static function findOrFail($id)
    {
        $result = static::find($id);
        if (!$result)
            throw new \Exception("Model not found");
        return $result;
    }

    public function save()
    {
        $db = Database::getInstance();
        if (isset($this->attributes[$this->primaryKey])) {
            // Update
            $db->update($this->table, $this->attributes, "{$this->primaryKey} = ?", [$this->attributes[$this->primaryKey]]);
        } else {
            // Insert
            $id = $db->insert($this->table, $this->attributes);
            $this->attributes[$this->primaryKey] = $id;
        }
    }

    public function update($attributes = [])
    {
        $this->attributes = array_merge($this->attributes, $attributes);
        $this->save();
    }

    public function first()
    {
        $sql = $this->buildQuery();
        return $this->fetchOne($sql, static::$queryParts['params']);
    }

    public function get()
    {
        $sql = $this->buildQuery();
        return $this->fetchAll($sql, static::$queryParts['params']);
    }

    public function count()
    {
        $sql = $this->buildQuery(true);
        $db = Database::getInstance();
        $result = $db->fetch($sql, static::$queryParts['params']);
        return $result['total'] ?? 0;
    }

    public function orderBy($col, $dir = 'ASC')
    {
        static::$queryParts['orderBy'] = "ORDER BY {$col} {$dir}";
        return $this;
    }
    public function latest()
    {
        return $this->orderBy('created_at', 'DESC');
    }

    public function with($relations)
    {
        // Placeholder: Real eager loading is hard. 
        // We will just return $this and rely on dynamic property access (lazy loading)
        // or manual handling in methods.
        return $this;
    }

    protected function buildQuery($count = false)
    {
        $whereStr = count(static::$queryParts['where']) > 0 ? "WHERE " . implode(' AND ', static::$queryParts['where']) : "";
        $orderStr = static::$queryParts['orderBy'] ?? "";

        if ($count) {
            return "SELECT COUNT(*) as total FROM {$this->table} {$whereStr}";
        }
        return "SELECT * FROM {$this->table} {$whereStr} {$orderStr}";
    }

    protected function fetchOne($sql, $params)
    {
        $db = Database::getInstance();
        $row = $db->fetch($sql, $params);
        return $row ? new static($row) : null;
    }

    protected function fetchAll($sql, $params)
    {
        $db = Database::getInstance();
        $rows = $db->fetchAll($sql, $params);
        $models = [];
        foreach ($rows as $row) {
            $models[] = new static($row);
        }
        return collect($models); // Return a collection wrapper if possible, or just array
    }

    // Relationship Helpers
    protected function belongsTo($class, $foreignKey)
    {
        return $class::find($this->attributes[$foreignKey]);
    }

    protected function hasMany($class, $foreignKey)
    {
        // Simple hack: return query builder seeded with where clause
        return $class::where($foreignKey, $this->attributes[$this->primaryKey]);
    }
}

// Simple Helper for Collections
function collect($items)
{
    return new class ($items) implements \ArrayAccess, \Countable, \IteratorAggregate {
        private $items;
        public function __construct($items)
        {
            $this->items = $items;
        }
        public function first()
        {
            return $this->items[0] ?? null;
        }
        public function count(): int
        {
            return count($this->items);
        }
        public function isEmpty()
        {
            return empty($this->items);
        }
        public function toArray()
        {
            return $this->items;
        }
        public function getIterator(): \Traversable
        {
            return new \ArrayIterator($this->items);
        }
        public function offsetExists($offset): bool
        {
            return isset($this->items[$offset]);
        }
        public function offsetGet($offset): mixed
        {
            return $this->items[$offset];
        }
        public function offsetSet($offset, $value): void
        {
            $this->items[$offset] = $value;
        }
        public function offsetUnset($offset): void
        {
            unset($this->items[$offset]);
        }
    };
}
