<?php
declare(strict_types=1);

namespace App\Core;

/**
 * Plugin lifecycle manager.
 * Boot order in Application::__construct():
 *   1. Database::boot()
 *   2. Schema::ensure()          ← creates plugins table
 *   3. PluginManager::boot()     ← loads active plugins (they register hooks/routes)
 *   4. new Router(...)           ← applies route filter from plugins
 */
class PluginManager
{
    private static ?self $instance = null;

    /** @var array<string, PluginInterface> slug → instance */
    private array $loaded = [];

    private function __construct() {}

    public static function getInstance(): self
    {
        return self::$instance ??= new self();
    }

    /**
     * Load all active plugins.  Called BEFORE Router is created.
     */
    public function boot(): void
    {
        // Register a universal PSR-4-style autoloader for all plugin namespaces.
        // Handles: Plugins\{PluginNs}\{SubPath} → plugins/{slug}/{SubPath}.php
        $pluginsDir = $this->pluginsDir();
        spl_autoload_register(static function (string $class) use ($pluginsDir): void {
            if (!str_starts_with($class, 'Plugins\\')) return;
            $parts = explode('\\', $class, 3); // ['Plugins', 'SomeNs', 'Sub\Path']
            if (count($parts) < 2 || !is_dir($pluginsDir)) return;
            $relPath = isset($parts[2])
                ? str_replace('\\', '/', $parts[2]) . '.php'
                : $parts[1] . '.php';
            foreach (array_diff(scandir($pluginsDir), ['.', '..']) as $slug) {
                $full = $pluginsDir . '/' . $slug . '/' . $relPath;
                if (file_exists($full)) { require_once $full; return; }
            }
        });

        foreach ($this->getActivePlugins() as $row) {
            $this->loadAndBoot($row['slug']);
        }

        // Register built-in plugin-manager routes via the 'routes' filter.
        // Priority 5 ensures they are merged before plugins add their own routes.
        Hooks::addFilter('routes', static function (array $routes): array {
            return array_merge($routes, [
                ['GET',  '/admin/plugins',                     ['PluginController', 'index'],      ['admin']],
                ['POST', '/admin/plugins/install',             ['PluginController', 'install'],    ['admin']],
                ['POST', '/admin/plugins/([^/]+)/activate',   ['PluginController', 'activate'],   ['admin']],
                ['POST', '/admin/plugins/([^/]+)/deactivate', ['PluginController', 'deactivate'], ['admin']],
                ['POST', '/admin/plugins/([^/]+)/uninstall',  ['PluginController', 'uninstall'],  ['admin']],
            ]);
        }, 5);
    }

    // ── Load ─────────────────────────────────────────────────────────────────

    private function loadAndBoot(string $slug): void
    {
        $dir      = $this->pluginsDir() . '/' . $slug;
        $mainFile = $dir . '/Plugin.php';
        $manifest = $this->readManifest($slug);

        if (!$manifest || !file_exists($mainFile)) return;

        require_once $mainFile;

        // Auto-require any additional PHP files the plugin declares
        foreach ($manifest['autoload'] ?? [] as $file) {
            $path = $dir . '/' . ltrim($file, '/');
            if (file_exists($path)) require_once $path;
        }

        $class = $manifest['class'] ?? null;
        if (!$class || !class_exists($class)) return;

        /** @var PluginInterface $instance */
        $instance = new $class();
        $instance->boot();
        $this->loaded[$slug] = $instance;
    }

    // ── Install ───────────────────────────────────────────────────────────────

    /**
     * Install a plugin from an uploaded ZIP file.
     *
     * The ZIP must contain a root folder named after the plugin slug:
     *   my-plugin/
     *     plugin.json
     *     Plugin.php
     *
     * Returns true on success, or an error string on failure.
     */
    public function install(string $tmpPath): true|string
    {
        if (!class_exists('ZipArchive')) {
            return 'پسوند ZipArchive در PHP فعال نیست. با مدیر سرور تماس بگیرید.';
        }

        $zip = new \ZipArchive();
        if ($zip->open($tmpPath) !== true) {
            return 'فایل آپلود‌شده یک ZIP معتبر نیست.';
        }

        // Find plugin.json inside the ZIP
        $manifestContent = null;
        $rootFolder      = null;
        for ($i = 0; $i < $zip->numFiles; $i++) {
            $name = $zip->getNameIndex($i);
            if (basename($name) === 'plugin.json') {
                $parts = explode('/', trim($name, '/'));
                if (count($parts) <= 2) {           // root or one level deep
                    $manifestContent = $zip->getFromIndex($i);
                    $rootFolder      = count($parts) === 2 ? $parts[0] : '';
                    break;
                }
            }
        }

        if ($manifestContent === null) {
            $zip->close();
            return 'فایل plugin.json در بسته یافت نشد.';
        }

        $manifest = json_decode($manifestContent, true);
        if (!is_array($manifest) || empty($manifest['id'])) {
            $zip->close();
            return 'فرمت plugin.json نامعتبر است (فیلد id الزامی است).';
        }

        $slug   = preg_replace('/[^a-z0-9\-_]/', '', strtolower($manifest['id']));
        $target = $this->pluginsDir() . '/' . $slug;

        // Extract to plugins dir
        if ($rootFolder !== null && $rootFolder !== '') {
            // ZIP has a root folder — extract to plugins/ and the folder becomes plugins/{rootFolder}/
            $zip->extractTo($this->pluginsDir());
            // Rename if folder name differs from slug
            if ($rootFolder !== $slug) {
                $extracted = $this->pluginsDir() . '/' . $rootFolder;
                if (is_dir($extracted)) rename($extracted, $target);
            }
        } else {
            // ZIP has no root folder — extract directly into plugins/{slug}/
            if (!is_dir($target)) mkdir($target, 0755, true);
            $zip->extractTo($target);
        }

        $zip->close();

        // Register / update in DB
        try {
            $pdo = Database::pdo();
            $existing = $pdo->prepare("SELECT id FROM plugins WHERE slug = ?");
            $existing->execute([$slug]);

            if ($existing->fetchColumn()) {
                $pdo->prepare(
                    "UPDATE plugins SET name=?, version=?, description=?, author=? WHERE slug=?"
                )->execute([
                    $manifest['name']        ?? $slug,
                    $manifest['version']     ?? '1.0.0',
                    $manifest['description'] ?? '',
                    $manifest['author']      ?? '',
                    $slug,
                ]);
            } else {
                $pdo->prepare(
                    "INSERT INTO plugins (slug,name,version,description,author,status) VALUES (?,?,?,?,?,'inactive')"
                )->execute([
                    $slug,
                    $manifest['name']        ?? $slug,
                    $manifest['version']     ?? '1.0.0',
                    $manifest['description'] ?? '',
                    $manifest['author']      ?? '',
                ]);
            }
            return true;
        } catch (\Throwable $e) {
            return 'خطای دیتابیس: ' . $e->getMessage();
        }
    }

    // ── Lifecycle ─────────────────────────────────────────────────────────────

    public function activate(string $slug): true|string
    {
        try {
            $pdo = Database::pdo();
            $row = $pdo->prepare("SELECT id FROM plugins WHERE slug = ?");
            $row->execute([$slug]);
            if (!$row->fetchColumn()) return 'افزونه یافت نشد.';

            // Load the plugin so activate() can run
            if (!isset($this->loaded[$slug])) {
                $this->loadAndBoot($slug);
            }
            if (isset($this->loaded[$slug])) {
                $this->loaded[$slug]->activate();
            }

            $pdo->prepare(
                "UPDATE plugins SET status='active', activated_at=NOW() WHERE slug=?"
            )->execute([$slug]);

            return true;
        } catch (\Throwable $e) {
            return 'خطا در فعال‌سازی: ' . $e->getMessage();
        }
    }

    public function deactivate(string $slug): true|string
    {
        try {
            $pdo = Database::pdo();
            if (isset($this->loaded[$slug])) {
                $this->loaded[$slug]->deactivate();
            }
            $pdo->prepare("UPDATE plugins SET status='inactive' WHERE slug=?")->execute([$slug]);
            return true;
        } catch (\Throwable $e) {
            return 'خطا در غیرفعال‌سازی: ' . $e->getMessage();
        }
    }

    public function uninstall(string $slug): true|string
    {
        try {
            // Run plugin's own cleanup
            if (!isset($this->loaded[$slug])) {
                $this->loadAndBoot($slug);
            }
            if (isset($this->loaded[$slug])) {
                $this->loaded[$slug]->uninstall();
                unset($this->loaded[$slug]);
            }

            Database::pdo()->prepare("DELETE FROM plugins WHERE slug=?")->execute([$slug]);

            // Remove plugin directory
            $dir = $this->pluginsDir() . '/' . $slug;
            if (is_dir($dir)) $this->rrmdir($dir);

            return true;
        } catch (\Throwable $e) {
            return 'خطا در حذف: ' . $e->getMessage();
        }
    }

    // ── Queries ───────────────────────────────────────────────────────────────

    public function getActivePlugins(): array
    {
        try {
            return Database::pdo()
                ->query("SELECT * FROM plugins WHERE status='active' ORDER BY id ASC")
                ->fetchAll();
        } catch (\Throwable $e) { return []; }
    }

    public function getAll(): array
    {
        try {
            return Database::pdo()
                ->query("SELECT * FROM plugins ORDER BY status DESC, name ASC")
                ->fetchAll();
        } catch (\Throwable $e) { return []; }
    }

    public function readManifest(string $slug): ?array
    {
        $path = $this->pluginsDir() . '/' . $slug . '/plugin.json';
        if (!file_exists($path)) return null;
        $data = json_decode(file_get_contents($path), true);
        return is_array($data) ? $data : null;
    }

    public function get(string $slug): ?PluginInterface
    {
        return $this->loaded[$slug] ?? null;
    }

    public function isLoaded(string $slug): bool
    {
        return isset($this->loaded[$slug]);
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    public function pluginsDir(): string
    {
        return defined('APP_ROOT') ? APP_ROOT . '/plugins' : dirname(__DIR__, 2) . '/plugins';
    }

    private function rrmdir(string $dir): void
    {
        foreach (array_diff(scandir($dir), ['.', '..']) as $f) {
            $p = $dir . '/' . $f;
            is_dir($p) ? $this->rrmdir($p) : unlink($p);
        }
        rmdir($dir);
    }
}
