<?php
declare(strict_types=1);

namespace App\Core;

abstract class Model
{
    protected static string $table = '';
    protected static string $primaryKey = 'id';
    protected static array $fillable = [];
    protected static bool $timestamps = true;
    protected static bool $softDelete = true;
    protected static bool $dispatchEvents = true;
    protected static bool $auditEnabled = true;
    protected static bool $autoTrackAuthor = true;

    protected array $attributes = [];
    protected bool $exists = false;

    public function __construct(array $attributes = [])
    {
        $this->attributes = $attributes;
        $this->exists = isset($attributes[static::$primaryKey]);
    }

    public static function query(): QueryBuilder
    {
        $qb = (new QueryBuilder())->table(static::$table);
        if (!static::$softDelete) {
            $qb = $qb->noSoftDelete();
        }
        return $qb;
    }

    public static function find(int $id): ?static
    {
        $row = static::query()->where(static::$primaryKey, '=', $id)->first();
        if ($row === null) {
            return null;
        }
        $instance = new static($row);
        $instance->exists = true;
        return $instance;
    }

    public static function findOrFail(int $id): static
    {
        $instance = static::find($id);
        if ($instance === null) {
            throw new \RuntimeException("Record not found in " . static::$table . " with id {$id}");
        }
        return $instance;
    }

    public static function create(array $data): static
    {
        $filtered = [];
        foreach ($data as $key => $value) {
            if (in_array($key, static::$fillable, true)) {
                $filtered[$key] = $value;
            }
        }

        if (static::$timestamps) {
            $filtered['created_at'] = date('Y-m-d H:i:s');
            $filtered['updated_at'] = date('Y-m-d H:i:s');
        }

        if (static::$autoTrackAuthor) {
            $employee = App::getInstance()->currentEmployee();
            if ($employee) {
                $filtered['created_by'] = $employee->id ?? ($employee['id'] ?? null);
            }
        }

        $db = App::getInstance()->db();
        $id = $db->insert(static::$table, $filtered);

        $instance = static::find($id);

        if (static::$dispatchEvents) {
            $singular = rtrim(static::$table, 's');
            EventBus::dispatch("{$singular}.created", ['id' => $id, 'data' => $filtered]);
        }

        return $instance;
    }

    public function save(): bool
    {
        $db = App::getInstance()->db();

        if ($this->exists) {
            return $this->update($this->attributes);
        }

        $id = $db->insert(static::$table, $this->attributes);
        $this->attributes[static::$primaryKey] = $id;
        $this->exists = true;
        return true;
    }

    public function update(array $data): bool
    {
        $filtered = [];
        foreach ($data as $key => $value) {
            if (in_array($key, static::$fillable, true)) {
                $filtered[$key] = $value;
            }
        }

        if (static::$timestamps) {
            $filtered['updated_at'] = date('Y-m-d H:i:s');
        }

        if (static::$autoTrackAuthor) {
            $employee = App::getInstance()->currentEmployee();
            if ($employee) {
                $filtered['updated_by'] = $employee->id ?? ($employee['id'] ?? null);
            }
        }

        $db = App::getInstance()->db();
        $db->update(static::$table, $filtered, '`' . static::$primaryKey . '` = ?', [$this->id]);

        $this->attributes = array_merge($this->attributes, $filtered);

        if (static::$dispatchEvents) {
            $singular = rtrim(static::$table, 's');
            EventBus::dispatch("{$singular}.updated", ['id' => $this->id, 'data' => $filtered]);
        }

        return true;
    }

    public function archive(): bool
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $data = [
            'is_archived' => 1,
            'archived_at' => date('Y-m-d H:i:s'),
        ];

        if ($employee) {
            $data['archived_by'] = $employee->id ?? ($employee['id'] ?? null);
        }

        $db->update(static::$table, $data, '`' . static::$primaryKey . '` = ?', [$this->id]);

        $this->attributes = array_merge($this->attributes, $data);

        if (static::$dispatchEvents) {
            $singular = rtrim(static::$table, 's');
            EventBus::dispatch("{$singular}.archived", ['id' => $this->id]);
        }

        return true;
    }

    public function toArray(): array
    {
        return $this->attributes;
    }

    public function __get(string $key)
    {
        return $this->attributes[$key] ?? null;
    }

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

    public function __isset(string $key): bool
    {
        return isset($this->attributes[$key]);
    }

    protected function beforeSave(): void {}
    protected function afterSave(): void {}
    protected function beforeCreate(): void {}
    protected function afterCreate(): void {}
}