<?php

declare(strict_types=1);

namespace App\Domain\Sysapi\Bridges;

use App\Domain\Sysapi\Actions\AllowedActions;
use App\Domain\Sysapi\Contracts\SysapiBridge;
use App\Domain\Sysapi\Contracts\SysapiResult;
use App\Domain\Sysapi\Exceptions\SysapiActionNotAllowedException;
use PHPUnit\Framework\Assert;

/**
 * Test-time sysapi implementation.
 *
 * Kullanım:
 *
 *   $fake = new FakeSysapi();
 *   $fake->queueResult(SysapiResult::success(['uid' => 10001]));
 *   $this->app->instance(SysapiBridge::class, $fake);
 *
 *   // ... test akışı ...
 *
 *   $fake->assertCalled('user-add');
 *   $fake->assertCalledWith('user-add', ['username' => 'onx_demo01']);
 *
 * Queue boşalırsa default başarılı sonuç döner (echo input).
 */
final class FakeSysapi implements SysapiBridge
{
    /** @var array<int, SysapiResult> */
    private array $queue = [];

    /** @var array<int, array{command: string, args: array<string, mixed>, timeout: int}> */
    public array $calls = [];

    public function run(string $command, array $args = [], int $timeoutSeconds = 60): SysapiResult
    {
        if (! AllowedActions::contains($command)) {
            throw SysapiActionNotAllowedException::forAction($command);
        }

        $this->calls[] = [
            'command' => $command,
            'args' => $args,
            'timeout' => $timeoutSeconds,
        ];

        if ($this->queue !== []) {
            return array_shift($this->queue);
        }

        // Default: action-aware mock data
        $data = match ($command) {
            'user-add' => (function () use ($args): array {
                $username = $args['username'] ?? 'unknown';
                $uid = rand(10000, 65000);

                return [
                    'username' => $username,
                    'uid' => $uid,
                    'gid' => $uid,
                    'home' => '/home/'.$username,
                    'skeleton' => 'cpanel-grade',
                    'skeleton_version' => '1',
                    'dirs_created' => 15,
                    'default_index' => true,
                    'default_htaccess' => true,
                    'bash_dotfiles' => true,
                    'selinux_applied' => true,
                    'created_at' => now()->toIso8601String(),
                ];
            })(),
            'user-verify-skeleton' => (function () use ($args): array {
                $username = $args['username'] ?? 'unknown';

                return [
                    'username' => $username,
                    'ok' => true,
                    'missing' => [],
                    'wrong_mode' => [],
                    'version' => '1',
                ];
            })(),
            'user-repair-skeleton' => (function () use ($args): array {
                $username = $args['username'] ?? 'unknown';

                return [
                    'username' => $username,
                    'repaired' => [],
                    'failed' => [],
                    'version' => '1',
                ];
            })(),
            'vhost-add' => (function () use ($args): array {
                // cPanel-style: vhost dosya adı normalde <username>-<domain>.conf,
                // sistem subdomain'lerde "is_system_subdomain":true bayrağıyla aynı pattern kullanılır.
                $username = $args['username'] ?? 'unknown';
                $domain = $args['domain'] ?? 'unknown';
                $vhostPath = "/etc/httpd/conf.d/sites/{$username}-{$domain}.conf";

                $base = [
                    'vhost_path' => $vhostPath,
                    'reloaded' => true,
                    'ssl_enabled' => (bool) ($args['ssl_enabled'] ?? false),
                    // Wildcard subdomain — bash dispatcher ServerAlias *.{domain} ekler.
                    // FakeSysapi sadece flag'ı echo eder, testler payload üzerinden assert eder.
                    'wildcard' => (bool) ($args['wildcard'] ?? false),
                ];

                // System subdomain meta — testler bunu assert eder
                if ($args['is_system_subdomain'] ?? false) {
                    $base['is_system_subdomain'] = true;
                    $base['subdomain_type'] = $args['subdomain_type'] ?? 'default';
                    $base['parent_domain'] = $args['parent_domain'] ?? null;
                }

                return $base;
            })(),
            'db-create' => [
                'db_name' => $args['db_name'] ?? 'unknown',
                'created' => true,
            ],
            // ── Faz R: DB engine version switcher ────────────────────────────
            'db-version-prepare' => (function () use ($args): array {
                $targetVersion = (string) ($args['target_version'] ?? '11.4');
                $targetEngine = (string) ($args['target_engine'] ?? 'mariadb');
                $backupMb = rand(50, 500);
                $ts = time();

                return [
                    'current_version' => 'MariaDB 10.11.6',
                    'current_engine' => 'mariadb',
                    'target_version' => $targetVersion,
                    'target_engine' => $targetEngine,
                    'backup_path' => "/var/backups/onox/pre-upgrade-{$ts}.sql.gz",
                    'backup_size_mb' => $backupMb,
                    'backup_sha256' => substr(hash('sha256', "preupgrade-{$ts}"), 0, 64),
                    'connections_active' => rand(0, 20),
                    'deprecated_features' => [
                        // Boş array — clean upgrade path için
                    ],
                    'estimated_downtime_min' => max(5, intdiv($backupMb, 100) + 3),
                    'go_no_go' => 'go',
                ];
            })(),
            'db-version-switch' => (function () use ($args): array {
                $targetVersion = (string) ($args['target_version'] ?? '11.4');
                $targetEngine = (string) ($args['target_engine'] ?? 'mariadb');
                $unit = $targetEngine === 'mysql' ? 'mysqld' : 'mariadb';
                $toLabel = $targetEngine === 'mysql' ? 'MySQL' : 'MariaDB';

                return [
                    'upgraded' => true,
                    'from' => 'MariaDB 10.11.6',
                    'to' => "{$toLabel} {$targetVersion}.2",
                    'service_unit' => $unit,
                    'mysql_upgrade_ok' => true,
                    'downtime_seconds' => rand(180, 480),
                    'backup_path' => (string) ($args['backup_path'] ?? '/var/backups/onox/pre-upgrade.sql.gz'),
                ];
            })(),
            'db-version-rollback' => (function () use ($args): array {
                $targetVersion = (string) ($args['target_version'] ?? '10.11');
                $targetEngine = (string) ($args['target_engine'] ?? 'mariadb');
                $toLabel = $targetEngine === 'mysql' ? 'MySQL' : 'MariaDB';

                return [
                    'rolled_back' => true,
                    'restored_to' => "{$toLabel} {$targetVersion}",
                    'backup_path_used' => (string) ($args['backup_path'] ?? '/var/backups/onox/pre-upgrade.sql.gz'),
                    'mysql_import_ok' => true,
                    'mysql_upgrade_ok' => true,
                    'downtime_seconds' => rand(120, 360),
                    'databases_restored' => rand(5, 50),
                ];
            })(),
            // ── Database — advanced (Faz I) ──────────────────────────────────
            'db-list-tables' => (function () use ($args): array {
                $dbName = (string) ($args['db_name'] ?? 'onx_demo_app');
                // WordPress-flavoured fake set (en yaygin senaryo); deterministic
                // seed sayesinde ayni DB icin ayni satir sayilari donerek
                // testler stabil olur.
                $seed = abs(crc32($dbName));
                $tables = [
                    ['name' => 'wp_options', 'engine' => 'InnoDB'],
                    ['name' => 'wp_posts', 'engine' => 'InnoDB'],
                    ['name' => 'wp_postmeta', 'engine' => 'InnoDB'],
                    ['name' => 'wp_users', 'engine' => 'InnoDB'],
                    ['name' => 'wp_usermeta', 'engine' => 'InnoDB'],
                    ['name' => 'wp_terms', 'engine' => 'InnoDB'],
                    ['name' => 'wp_term_taxonomy', 'engine' => 'InnoDB'],
                    ['name' => 'wp_term_relationships', 'engine' => 'InnoDB'],
                    ['name' => 'wp_comments', 'engine' => 'InnoDB'],
                    ['name' => 'wp_commentmeta', 'engine' => 'InnoDB'],
                ];
                $rows = [];
                foreach ($tables as $i => $t) {
                    $rowCount = (($seed + $i * 731) % 50000) + 1;
                    $sizeBytes = $rowCount * (($seed + $i * 13) % 600 + 80);
                    $rows[] = [
                        'name' => $t['name'],
                        'rows' => $rowCount,
                        'size_bytes' => $sizeBytes,
                        'engine' => $t['engine'],
                        'collation' => 'utf8mb4_unicode_ci',
                    ];
                }

                return [
                    'db_name' => $dbName,
                    'tables' => $rows,
                ];
            })(),
            'db-optimize-table' => [
                'db_name' => $args['db_name'] ?? 'unknown',
                'table' => $args['table'] ?? null,
                'optimized' => true,
            ],
            'db-repair-table' => [
                'db_name' => $args['db_name'] ?? 'unknown',
                'table' => $args['table'] ?? null,
                'repaired' => true,
            ],
            'db-check-table' => [
                'db_name' => $args['db_name'] ?? 'unknown',
                'table' => $args['table'] ?? null,
                'checked' => true,
                'status' => 'OK',
                'output' => 'OK',
            ],
            'db-analyze-table' => [
                'db_name' => $args['db_name'] ?? 'unknown',
                'table' => $args['table'] ?? null,
                'analyzed' => true,
            ],
            'db-truncate-table' => [
                'db_name' => $args['db_name'] ?? 'unknown',
                'table' => $args['table'] ?? 'unknown',
                'truncated' => true,
            ],
            'db-drop-table' => [
                'db_name' => $args['db_name'] ?? 'unknown',
                'table' => $args['table'] ?? 'unknown',
                'dropped' => true,
            ],
            'db-dump' => (function () use ($args): array {
                $dbName = (string) ($args['db_name'] ?? 'onx_demo_app');
                $ts = time();

                return [
                    'db_name' => $dbName,
                    'file_path' => "/var/lib/onoxsoft/db-dumps/db-dump-{$dbName}-{$ts}.sql.gz",
                    'size_bytes' => (abs(crc32($dbName)) % 5_000_000) + 100_000,
                    'expires_at' => now()->addDays(30)->toIso8601String(),
                ];
            })(),
            'db-import' => [
                'db_name' => $args['db_name'] ?? 'unknown',
                'imported' => true,
                'rows' => abs(crc32((string) ($args['db_name'] ?? 'db'))) % 50000 + 100,
                'duration_seconds' => round((abs(crc32((string) ($args['db_name'] ?? 'db'))) % 800) / 100, 2),
            ],
            'db-search' => (function () use ($args): array {
                $dbName = (string) ($args['db_name'] ?? 'onx_demo_app');
                $pattern = strtolower((string) ($args['pattern'] ?? ''));
                $scope = (string) ($args['scope'] ?? 'names');
                $allTables = [
                    'wp_options', 'wp_posts', 'wp_postmeta', 'wp_users', 'wp_usermeta',
                    'wp_terms', 'wp_term_taxonomy', 'wp_term_relationships',
                    'wp_comments', 'wp_commentmeta',
                ];
                $matches = [];
                foreach ($allTables as $t) {
                    if ($pattern === '' || str_contains($t, $pattern)) {
                        if ($scope === 'names') {
                            $matches[] = ['table' => $t];
                        } else {
                            $matches[] = ['table' => $t, 'column' => $pattern.'_id', 'type' => 'bigint'];
                        }
                    }
                }

                return [
                    'db_name' => $dbName,
                    'pattern' => $args['pattern'] ?? '',
                    'scope' => $scope,
                    'matches' => $matches,
                ];
            })(),
            'zone-add' => [
                'zone' => $args['zone'] ?? $args['domain'] ?? 'unknown',
                // cPanel-style full auto-provisioning: SOA + 2× NS + 28 template kayıt = 31
                // (DKIM eklendiğinde 32+). Eski "5" değeri SOA+NS only davranışındandı.
                'records_count' => 31,
                'created' => true,
            ],
            'zone-reload' => [
                'zone' => $args['zone'] ?? $args['domain'] ?? 'unknown',
                'reloaded' => true,
            ],
            'dnssec-enable' => (function () use ($args): array {
                $zone = (string) ($args['zone'] ?? $args['domain'] ?? 'unknown');
                $algo = (string) ($args['algo'] ?? 'ECDSAP256SHA256');
                $kskTag = random_int(10000, 65535);
                $zskTag = random_int(10000, 65535);
                // SHA-1 (digest_type=1), SHA-256 (digest_type=2), GOST (digest_type=3), SHA-384 (digest_type=4)
                $digest256 = strtoupper(hash('sha256', "{$zone}|ksk|{$kskTag}"));
                $digest384 = strtoupper(hash('sha384', "{$zone}|ksk|{$kskTag}"));

                return [
                    'zone' => $zone,
                    'algo' => $algo,
                    'ds_records' => [
                        ['keytag' => $kskTag, 'algo' => 13, 'digest_type' => 2, 'digest' => $digest256],
                        ['keytag' => $kskTag, 'algo' => 13, 'digest_type' => 4, 'digest' => $digest384],
                    ],
                    'keys' => [
                        'ksk' => ['id' => 1, 'tag' => $kskTag, 'algo' => $algo, 'bits' => 256, 'active' => true, 'flags' => 257],
                        'zsk' => ['id' => 2, 'tag' => $zskTag, 'algo' => $algo, 'bits' => 256, 'active' => true, 'flags' => 256],
                    ],
                    'enabled_at' => now()->toIso8601String(),
                ];
            })(),
            'dnssec-disable' => [
                'zone' => $args['zone'] ?? $args['domain'] ?? 'unknown',
                'disabled' => true,
                'keys_removed' => 2,
            ],
            'dnssec-show' => (function () use ($args): array {
                $zone = (string) ($args['zone'] ?? $args['domain'] ?? 'unknown');
                $kskTag = (int) (crc32($zone.'|ksk') % 55000 + 10000);
                $zskTag = (int) (crc32($zone.'|zsk') % 55000 + 10000);
                $digest256 = strtoupper(hash('sha256', "{$zone}|ksk|{$kskTag}"));
                $digest384 = strtoupper(hash('sha384', "{$zone}|ksk|{$kskTag}"));

                return [
                    'zone' => $zone,
                    'dnssec_enabled' => true,
                    'algo' => 'ECDSAP256SHA256',
                    'ds_records' => [
                        ['keytag' => $kskTag, 'algo' => 13, 'digest_type' => 2, 'digest' => $digest256],
                        ['keytag' => $kskTag, 'algo' => 13, 'digest_type' => 4, 'digest' => $digest384],
                    ],
                    'keys' => [
                        'ksk' => ['id' => 1, 'tag' => $kskTag, 'algo' => 'ECDSAP256SHA256', 'bits' => 256, 'active' => true, 'flags' => 257],
                        'zsk' => ['id' => 2, 'tag' => $zskTag, 'algo' => 'ECDSAP256SHA256', 'bits' => 256, 'active' => true, 'flags' => 256],
                    ],
                ];
            })(),
            'dnssec-key-rollover' => (function () use ($args): array {
                $zone = (string) ($args['zone'] ?? $args['domain'] ?? 'unknown');
                $newTag = random_int(10000, 65535);
                $digest256 = strtoupper(hash('sha256', "{$zone}|ksk-rolled|{$newTag}"));
                $digest384 = strtoupper(hash('sha384', "{$zone}|ksk-rolled|{$newTag}"));

                return [
                    'zone' => $zone,
                    'rolled' => true,
                    'new_ksk_tag' => $newTag,
                    'old_ksk_active_until' => now()->addDays(7)->toIso8601String(),
                    'ds_records' => [
                        ['keytag' => $newTag, 'algo' => 13, 'digest_type' => 2, 'digest' => $digest256],
                        ['keytag' => $newTag, 'algo' => 13, 'digest_type' => 4, 'digest' => $digest384],
                    ],
                    'rolled_at' => now()->toIso8601String(),
                ];
            })(),
            'cert-issue' => (function () use ($args): array {
                $primary = (string) ($args['domain'] ?? 'unknown');
                $altNames = (array) ($args['alt_names'] ?? []);
                // cPanel-style AutoSSL: primary + sistem subdomain'leri tek cert'te
                $sans = array_values(array_unique(array_merge([$primary], $altNames)));

                return [
                    'cert_path' => "/etc/letsencrypt/live/{$primary}/fullchain.pem",
                    'key_path' => "/etc/letsencrypt/live/{$primary}/privkey.pem",
                    'chain_path' => "/etc/letsencrypt/live/{$primary}/chain.pem",
                    'expires_at' => now()->addDays(90)->toIso8601String(),
                    'issuer' => "Let's Encrypt",
                    'sans' => $sans,
                    'autossl' => (bool) ($args['autossl'] ?? false),
                    'fingerprint_sha256' => hash('sha256', $primary.implode(',', $altNames).time()),
                ];
            })(),
            'csr-generate' => (function () use ($args): array {
                $cn = (string) ($args['common_name'] ?? 'example.com');
                $keySize = $args['key_size'] ?? 2048;
                // Deterministic per-CN mock so the same CN yields the same payload across calls (test stability).
                $seed = hash('sha256', "csr|{$cn}|{$keySize}");
                $b64 = strtr(base64_encode(hex2bin($seed).hex2bin($seed)), '+/', '-_');
                $chunks = chunk_split($b64, 64, "\n");

                return [
                    'csr' => "-----BEGIN CERTIFICATE REQUEST-----\n{$chunks}-----END CERTIFICATE REQUEST-----",
                    'private_key' => is_string($keySize) && str_starts_with($keySize, 'ec-')
                        ? "-----BEGIN EC PRIVATE KEY-----\n{$chunks}-----END EC PRIVATE KEY-----"
                        : "-----BEGIN RSA PRIVATE KEY-----\n{$chunks}-----END RSA PRIVATE KEY-----",
                    'key_size' => $keySize,
                    'common_name' => $cn,
                ];
            })(),
            'cert-install-manual' => (function () use ($args): array {
                $domain = (string) ($args['domain'] ?? 'example.com');

                return [
                    'domain' => $domain,
                    'cert_path' => "/etc/onoxsoft/ssl/manual/{$domain}/cert.pem",
                    'key_path' => "/etc/onoxsoft/ssl/manual/{$domain}/privkey.pem",
                    'chain_path' => "/etc/onoxsoft/ssl/manual/{$domain}/chain.pem",
                    'fullchain_path' => "/etc/onoxsoft/ssl/manual/{$domain}/fullchain.pem",
                    'expires_at' => now()->addDays(365)->toIso8601String(),
                    'issued_at' => now()->toIso8601String(),
                    'issuer' => 'C = TR, O = Sectigo Limited, CN = Sectigo RSA Domain Validation Secure Server CA',
                    'subject' => "CN = {$domain}",
                    'serial_number' => strtoupper(bin2hex(random_bytes(8))),
                    'fingerprint_sha256' => strtoupper(implode(':', str_split(hash('sha256', $domain.time()), 2))),
                ];
            })(),
            // ── WordPress Toolkit ────────────────────────────────────────
            'wp-cli' => (function () use ($args): array {
                $command = $args['command'] ?? '';
                if (str_starts_with($command, 'plugin list')) {
                    return ['parsed' => [
                        ['name' => 'Akismet', 'slug' => 'akismet', 'version' => '5.3.1', 'update_version' => '', 'status' => 'active'],
                        ['name' => 'Yoast SEO', 'slug' => 'wordpress-seo', 'version' => '22.7', 'update_version' => '', 'status' => 'active'],
                        ['name' => 'Elementor', 'slug' => 'elementor', 'version' => '3.20.3', 'update_version' => '3.21.0', 'status' => 'active'],
                    ]];
                }
                if (str_starts_with($command, 'theme list')) {
                    return ['parsed' => [
                        ['name' => 'Twenty Twenty-Four', 'slug' => 'twentytwentyfour', 'version' => '1.0', 'update_version' => '1.1', 'status' => 'active'],
                        ['name' => 'Astra', 'slug' => 'astra', 'version' => '4.7.0', 'update_version' => '4.7.2', 'status' => 'inactive'],
                    ]];
                }
                if (str_starts_with($command, 'user list')) {
                    return ['parsed' => [
                        ['ID' => 1, 'user_login' => 'admin', 'user_email' => 'admin@site.com', 'roles' => 'administrator', 'registered' => '2024-01-01 00:00:00'],
                    ]];
                }
                if (str_starts_with($command, 'cron event list')) {
                    return ['parsed' => [
                        ['hook' => 'wp_scheduled_delete', 'next_run_gmt' => now()->addHour()->toIso8601String(), 'interval' => 43200],
                        ['hook' => 'wp_update_themes', 'next_run_gmt' => now()->addDay()->toIso8601String(), 'interval' => 43200],
                    ]];
                }
                if (str_starts_with($command, 'core version')) {
                    return ['output' => '6.5.3'];
                }

                return ['success' => true, 'output' => 'OK (mock)', 'parsed' => []];
            })(),
            'wp-staging-clone', 'wp-staging-push' => [
                'success' => true,
                'files_count' => rand(1000, 5000),
                'duration_s' => rand(10, 60),
            ],

            // ── WHM Transfer Tool — uzak server connection test ──────────
            // Production: ssh -o ConnectTimeout=10 + uname -a + cpanel -V kontrolü
            'cpanel-test-connection' => (function () use ($args): array {
                $host = (string) ($args['host'] ?? 'cpanel.example.com');
                $port = (int) ($args['port'] ?? 22);

                return [
                    'host' => $host,
                    'port' => $port,
                    'connected' => true,
                    'ssh_version' => 'OpenSSH_8.7p1',
                    'kernel' => 'Linux cpanel.example.com 4.18.0-553 x86_64',
                    'cpanel_version' => '11.110.0.6',
                    'whm_user' => $args['login_user'] ?? 'root',
                    'login_type' => $args['login_user'] === 'root' ? 'root' : 'user',
                    'auth_method' => $args['auth_method'] ?? 'password',
                    'tested_at' => now()->toIso8601String(),
                ];
            })(),

            // ── WHM Transfer Tool — listaccts (tüm cPanel hesapları) ──────
            // Production: ssh root@<host> "whmapi1 listaccts --output=json"
            // veya fallback: ssh root@<host> "/scripts/listaccts" (line-based parse)
            'cpanel-list-remote-accounts' => (function () use ($args): array {
                $host = (string) ($args['host'] ?? 'cpanel.example.com');
                // Mock: 8 örnek hesap, gerçekçi disk/db sayıları
                $samples = [];
                $usernames = ['acmecorp', 'mavimuze', 'kosehosting', 'tarlamarket', 'denemehosting',
                              'webteam01', 'mahmutblog', 'kursdunya'];
                foreach ($usernames as $i => $u) {
                    $samples[] = [
                        'username' => $u,
                        'domain' => "{$u}.com",
                        'email' => "admin@{$u}.com",
                        'plan' => $i < 3 ? 'unlimited' : 'starter',
                        'disk_used_mb' => random_int(50, 8_500),
                        'disk_quota_mb' => $i < 3 ? 'unlimited' : 10_000,
                        'bandwidth_used_mb' => random_int(1_000, 50_000),
                        'bandwidth_quota_mb' => $i < 3 ? 'unlimited' : 100_000,
                        'mysql_db_count' => random_int(0, 8),
                        'addon_domains' => random_int(0, 5),
                        'subdomains' => random_int(0, 12),
                        'email_accounts' => random_int(0, 25),
                        'suspended' => $i === 6, // mahmutblog suspended
                        'suspended_reason' => $i === 6 ? 'late_payment' : null,
                        'partition' => 'home',
                        'ip' => '193.140.10.'.(20 + $i),
                        'theme' => 'jupiter',
                        'creation_date' => now()->subDays(random_int(30, 800))->toDateString(),
                    ];
                }

                return [
                    'host' => $host,
                    'total_accounts' => count($samples),
                    'total_disk_used_mb' => array_sum(array_column($samples, 'disk_used_mb')),
                    'whmapi_version' => '11.110',
                    'accounts' => $samples,
                    'listed_at' => now()->toIso8601String(),
                ];
            })(),

            // ════════════════════════════════════════════════════════════
            //  DirectAdmin Transfer Tool (Phase 2 — multi-source)
            // ════════════════════════════════════════════════════════════
            'directadmin-test-connection' => (function () use ($args): array {
                return [
                    'host' => $args['host'] ?? 'da.example.com',
                    'port' => (int) ($args['port'] ?? 22),
                    'connected' => true,
                    'ssh_version' => 'OpenSSH_8.7p1',
                    'da_version' => '1.66.5',
                    'da_build' => '2628',
                    'da_user' => 'admin',
                    'login_type' => $args['login_user'] === 'root' ? 'root' : 'user',
                    'auth_method' => $args['auth_method'] ?? 'password',
                    'os' => 'AlmaLinux 9.4',
                    'tested_at' => now()->toIso8601String(),
                ];
            })(),
            'directadmin-list-accounts' => (function () use ($args): array {
                $usernames = ['demo01', 'demo02', 'webhost1', 'webhost2', 'reseller_a'];
                $samples = [];
                foreach ($usernames as $i => $u) {
                    $samples[] = [
                        'username' => $u,
                        'domain' => "{$u}.com.tr",
                        'email' => "admin@{$u}.com.tr",
                        'plan' => $i < 2 ? 'unlimited' : 'standard',
                        'disk_used_mb' => random_int(100, 10_000),
                        'disk_quota_mb' => $i < 2 ? 'unlimited' : 20_000,
                        'bandwidth_used_mb' => random_int(2_000, 80_000),
                        'bandwidth_quota_mb' => $i < 2 ? 'unlimited' : 200_000,
                        'mysql_db_count' => random_int(0, 6),
                        'addon_domains' => random_int(0, 4),
                        'subdomains' => random_int(0, 10),
                        'email_accounts' => random_int(0, 20),
                        'suspended' => false,
                        'creator' => 'admin',
                        'usertype' => $u === 'reseller_a' ? 'reseller' : 'user',
                        'creation_date' => now()->subDays(random_int(30, 600))->toDateString(),
                        'ip' => '193.140.20.'.(40 + $i),
                    ];
                }

                return [
                    'host' => $args['host'] ?? 'da.example.com',
                    'total_accounts' => count($samples),
                    'total_disk_used_mb' => array_sum(array_column($samples, 'disk_used_mb')),
                    'da_version' => '1.66.5',
                    'accounts' => $samples,
                    'listed_at' => now()->toIso8601String(),
                ];
            })(),
            'directadmin-pull-remote' => (function () use ($args): array {
                $user = (string) ($args['source_username'] ?? 'demo01');
                $ts = now()->format('Ymd-His');
                $localPath = "migrations/da-{$user}-{$ts}.tar.gz";

                return [
                    'source_host' => $args['host'] ?? 'da.example.com',
                    'source_port' => (int) ($args['port'] ?? 22),
                    'source_username' => $user,
                    'backup_script' => '/usr/local/directadmin/scripts/admin_backup_user.sh',
                    'backup_duration_s' => random_int(30, 400),
                    'rsync_duration_s' => random_int(20, 250),
                    'local_path' => $localPath,
                    'tar_size_bytes' => random_int(20_000_000, 4_000_000_000),
                    'da_version' => '1.66.5',
                    'pulled_at' => now()->toIso8601String(),
                ];
            })(),
            'directadmin-import' => (function () use ($args): array {
                return [
                    'archive_path' => $args['archive_path'] ?? '/var/lib/onox/migrations/unknown.tar.gz',
                    'parsed' => true,
                    'source_user' => $args['source_user'] ?? 'demo01',
                    'manifests' => [
                        'domains' => '/tmp/da-domains.json',
                        'databases' => '/tmp/da-databases.json',
                        'email_accounts' => '/tmp/da-emails.json',
                        'dns_zones' => '/tmp/da-dns.json',
                        'cron_jobs' => '/tmp/da-cron.json',
                        'ftp_users' => '/tmp/da-ftp.json',
                    ],
                    'imported' => [
                        'domains' => random_int(1, 5),
                        'databases' => random_int(0, 6),
                        'email_accounts' => random_int(0, 20),
                    ],
                    'duration_s' => random_int(30, 200),
                ];
            })(),

            // ════════════════════════════════════════════════════════════
            //  Plesk Transfer Tool (Phase 2 — multi-source)
            // ════════════════════════════════════════════════════════════
            'plesk-test-connection' => (function () use ($args): array {
                return [
                    'host' => $args['host'] ?? 'plesk.example.com',
                    'port' => (int) ($args['port'] ?? 22),
                    'connected' => true,
                    'ssh_version' => 'OpenSSH_8.7p1',
                    'plesk_version' => 'Plesk Obsidian 18.0.61',
                    'plesk_build' => '18.0.61 #1',
                    'auth_method' => $args['auth_method'] ?? 'password',
                    'os' => 'Ubuntu 22.04 LTS',
                    'login_type' => $args['login_user'] === 'root' ? 'root' : 'user',
                    'tested_at' => now()->toIso8601String(),
                ];
            })(),
            'plesk-list-subscriptions' => (function () use ($args): array {
                // Plesk "subscription" = bir hesabın bir hosting paketi (cPanel "account" karşılığı)
                $names = ['acmestudio', 'kayseripetshop', 'mardinbazar', 'butikgaleri', 'meksika', 'denedurum'];
                $samples = [];
                foreach ($names as $i => $name) {
                    $samples[] = [
                        'subscription_id' => 1000 + $i,
                        'username' => $name,           // Plesk system user
                        'domain' => "{$name}.com",
                        'email' => "owner@{$name}.com",
                        'plan' => $i < 2 ? 'Business' : 'Personal',
                        'disk_used_mb' => random_int(200, 12_000),
                        'disk_quota_mb' => $i < 2 ? 'unlimited' : 25_000,
                        'bandwidth_used_mb' => random_int(3_000, 60_000),
                        'bandwidth_quota_mb' => $i < 2 ? 'unlimited' : 150_000,
                        'mysql_db_count' => random_int(0, 8),
                        'addon_domains' => random_int(0, 3),
                        'subdomains' => random_int(0, 8),
                        'email_accounts' => random_int(0, 15),
                        'suspended' => $i === 4,    // meksika suspended
                        'suspended_reason' => $i === 4 ? 'pa_disabled' : null,
                        'creation_date' => now()->subDays(random_int(30, 700))->toDateString(),
                        'ip' => '193.140.30.'.(60 + $i),
                        'php_version' => '8.2',
                    ];
                }

                return [
                    'host' => $args['host'] ?? 'plesk.example.com',
                    'total_accounts' => count($samples),
                    'total_disk_used_mb' => array_sum(array_column($samples, 'disk_used_mb')),
                    'plesk_version' => 'Plesk Obsidian 18.0.61',
                    'accounts' => $samples,
                    'listed_at' => now()->toIso8601String(),
                ];
            })(),
            'plesk-pull-remote' => (function () use ($args): array {
                $user = (string) ($args['source_username'] ?? 'acmestudio');
                $ts = now()->format('Ymd-His');
                $localPath = "migrations/plesk-{$user}-{$ts}.tar.gz";

                return [
                    'source_host' => $args['host'] ?? 'plesk.example.com',
                    'source_port' => (int) ($args['port'] ?? 22),
                    'source_username' => $user,
                    'backup_script' => '/usr/local/psa/bin/pleskbackup',
                    'export_duration_s' => random_int(60, 600),
                    'rsync_duration_s' => random_int(30, 300),
                    'local_path' => $localPath,
                    'tar_size_bytes' => random_int(50_000_000, 5_000_000_000),
                    'plesk_version' => 'Plesk Obsidian 18.0.61',
                    'backup_format' => 'pmbf',     // Plesk Migration & Backup Format
                    'pulled_at' => now()->toIso8601String(),
                ];
            })(),
            'plesk-import' => (function () use ($args): array {
                return [
                    'archive_path' => $args['archive_path'] ?? '/var/lib/onox/migrations/unknown.tar.gz',
                    'parsed' => true,
                    'source_user' => $args['source_user'] ?? 'acmestudio',
                    'manifests' => [
                        'subscriptions' => '/tmp/plesk-subscriptions.json',
                        'databases' => '/tmp/plesk-databases.json',
                        'email_accounts' => '/tmp/plesk-emails.json',
                        'dns_zones' => '/tmp/plesk-dns.json',
                        'cron_jobs' => '/tmp/plesk-cron.json',
                    ],
                    'imported' => [
                        'subscriptions' => random_int(1, 3),
                        'databases' => random_int(0, 8),
                        'email_accounts' => random_int(0, 15),
                    ],
                    'duration_s' => random_int(60, 300),
                ];
            })(),

            // ── cPanel remote pull via SSH ───────────────────────────────
            // Production: ssh root@<host> "/scripts/pkgacct <user>" + rsync
            // /home/cpmove-<user>.tar.gz → /var/lib/onox/migrations/<user>-<ts>.tar.gz
            'cpanel-pull-remote' => (function () use ($args): array {
                $host = (string) ($args['source_host'] ?? 'cpanel.example.com');
                $sourceUser = (string) ($args['source_username'] ?? 'unknown');
                $port = (int) ($args['source_port'] ?? 22);
                $ts = now()->format('Ymd-His');
                $localPath = "migrations/cpmove-{$sourceUser}-{$ts}.tar.gz";

                return [
                    'source_host' => $host,
                    'source_port' => $port,
                    'source_username' => $sourceUser,
                    'pkgacct_duration_s' => random_int(60, 600),
                    'rsync_duration_s' => random_int(30, 300),
                    'local_path' => $localPath,
                    'tar_size_bytes' => random_int(50_000_000, 5_000_000_000),
                    'pkgacct_version' => '11.110',
                    'pulled_at' => now()->toIso8601String(),
                ];
            })(),

            // ── cPanel pkgacct import ────────────────────────────────────
            'cpanel-import' => (function () use ($args): array {
                $mode = (string) ($args['mode'] ?? 'inspect');
                $source = (string) ($args['source_user'] ?? 'acmeuser');
                $target = (string) ($args['target_username'] ?? 'onx_acme01');
                $dryRun = (bool) ($args['dry_run'] ?? false);
                $skip = (array) ($args['skip'] ?? []);

                $isSkip = fn (string $s): bool => in_array($s, $skip, true);
                $staging = sys_get_temp_dir().'/onx-cpanel-fake-'.uniqid();

                // Best-effort: write minimal JSON manifests so the engine can
                // ingest them in tests; tolerate failure on read-only mounts.
                $manifests = [
                    'emails' => $staging.'/emails.json',
                    'forwarders' => $staging.'/forwarders.json',
                    'databases' => $staging.'/databases.json',
                    'dns_zones' => $staging.'/dns_zones.json',
                    'ssl_certs' => $staging.'/ssl_certs.json',
                    'cron_jobs' => $staging.'/cron_jobs.json',
                ];
                @mkdir($staging, 0750, recursive: true);
                $writeIf = function (string $path, array $rows): void {
                    @file_put_contents($path, json_encode($rows, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
                };

                $emails = $isSkip('email') ? [] : [
                    [
                        'local' => 'info',
                        'domain' => $source.'.example.com',
                        'password_hash' => '{SHA512-CRYPT}$6$abc$y8u8',
                        'quota_mb' => 1024,
                    ],
                ];
                $forwarders = $isSkip('email') ? [] : [
                    [
                        'source' => 'sales@'.$source.'.example.com',
                        'destinations' => 'sales@other.example.com',
                        'domain' => $source.'.example.com',
                    ],
                ];
                $databases = $isSkip('mysql') ? [] : [
                    [
                        'original_name' => $source.'_wp',
                        'new_name' => $target.'_wp',
                        'sql_file' => $staging.'/mysql/'.$source.'_wp.sql',
                        'size_bytes' => 2_500_000,
                    ],
                ];
                $dnsZones = $isSkip('dns') ? [] : [
                    [
                        'domain' => $source.'.example.com',
                        'record_count' => 4,
                        'records' => [
                            ['name' => $source.'.example.com', 'type' => 'A', 'ttl' => 14400, 'prio' => 0, 'content' => '203.0.113.10'],
                            ['name' => 'www.'.$source.'.example.com', 'type' => 'CNAME', 'ttl' => 14400, 'prio' => 0, 'content' => $source.'.example.com.'],
                            ['name' => $source.'.example.com', 'type' => 'MX', 'ttl' => 14400, 'prio' => 10, 'content' => 'mail.'.$source.'.example.com.'],
                            ['name' => $source.'.example.com', 'type' => 'TXT', 'ttl' => 14400, 'prio' => 0, 'content' => '"v=spf1 a mx ~all"'],
                        ],
                    ],
                ];
                $sslCerts = $isSkip('ssl') ? [] : [
                    [
                        'domain' => $source.'.example.com',
                        'cert_path' => $staging.'/ssl/certs/'.$source.'.example.com.crt',
                        'key_path' => $staging.'/ssl/keys/'.$source.'.example.com.key',
                        'expires_at' => now()->addDays(90)->toRfc2822String(),
                    ],
                ];
                $cronJobs = $isSkip('cron') ? [] : [
                    [
                        'minute' => '0',
                        'hour' => '3',
                        'day' => '*',
                        'month' => '*',
                        'weekday' => '*',
                        'command' => 'php /home/'.$target.'/cron/backup.php',
                        'expression' => '0 3 * * *',
                    ],
                ];

                $writeIf($manifests['emails'], $emails);
                $writeIf($manifests['forwarders'], $forwarders);
                $writeIf($manifests['databases'], $databases);
                $writeIf($manifests['dns_zones'], $dnsZones);
                $writeIf($manifests['ssl_certs'], $sslCerts);
                $writeIf($manifests['cron_jobs'], $cronJobs);

                return [
                    'source_user' => $source,
                    'target_user' => $target,
                    'pkgacct_version' => '11.110',
                    'primary_domain' => $source.'.example.com',
                    'staging_dir' => $staging,
                    'mode' => $mode,
                    'dry_run' => $dryRun,
                    'skip' => $skip,
                    'imported' => [
                        'homedir_bytes' => $isSkip('homedir') ? 0 : 483_920_104,
                        'emails' => count($emails),
                        'forwarders' => count($forwarders),
                        'databases' => count($databases),
                        'dns_zones' => count($dnsZones),
                        'ssl_certs' => count($sslCerts),
                        'cron_jobs' => count($cronJobs),
                    ],
                    'manifests' => $manifests,
                ];
            })(),

            // ── Backup core (Phase 1.5) ──────────────────────────────────
            'backup-run' => (function () use ($args): array {
                $username = $args['username'] ?? 'unknown';
                $type = $args['type'] ?? 'full';
                $ts = now()->format('Ymd-His');
                $sizeBytes = rand(50_000_000, 1_500_000_000);
                $path = "/var/backups/onoxsoft/{$username}/{$username}-{$type}-{$ts}.tar.gz";

                $included = ['files' => (bool) ($args['include_dbs'] ?? false), 'home' => true];
                if ($args['include_mail'] ?? false) {
                    $included['mail'] = true;
                }
                if ($args['include_dbs'] ?? false) {
                    $included['databases'] = ['count' => rand(1, 5), 'size_bytes' => rand(5_000_000, 200_000_000)];
                }
                if ($args['include_dns'] ?? false) {
                    $included['dns_zones'] = rand(1, 5);
                }

                return [
                    'file_path' => $path,
                    'path' => $path,
                    'size_bytes' => $sizeBytes,
                    'sha256' => hash('sha256', $path.$ts),
                    'contents' => $included,
                    'included' => $included,
                    'duration_seconds' => rand(15, 300),
                ];
            })(),
            'backup-restore' => [
                'restored' => true,
                'backup_id' => $args['backup_id'] ?? null,
                'username' => $args['username'] ?? 'unknown',
                'files_restored' => rand(500, 25000),
                'databases_restored' => rand(0, 5),
                'duration_seconds' => rand(20, 600),
            ],
            'backup-run-incremental' => (function () use ($args): array {
                $username = $args['username'] ?? 'unknown';
                $ts = now()->format('Ymd-His');
                $parentPath = (string) ($args['parent_path'] ?? '');
                // Incremental: sadece değişen dosyalar fiilen kopyalanır, gerisi hard-link
                $changedFiles = rand(15, 500);
                $hardLinkedFiles = rand(10_000, 50_000);
                $changedBytes = rand(2_000_000, 50_000_000);
                $path = "/var/backups/onoxsoft/{$username}/{$username}-incremental-{$ts}";

                return [
                    'file_path' => $path,
                    'path' => $path,
                    'size_bytes' => $changedBytes,    // Sadece değişen kısmın boyutu
                    'apparent_size_bytes' => rand(500_000_000, 2_000_000_000),  // du -h ile görünen toplam
                    'sha256' => hash('sha256', $path.$ts),
                    'parent_path' => $parentPath,
                    'changed_files' => $changedFiles,
                    'hardlinked_files' => $hardLinkedFiles,
                    'rsync_method' => 'link-dest',
                    'contents' => [
                        'home' => true,
                        'changed_files' => $changedFiles,
                        'hardlinked_files' => $hardLinkedFiles,
                        'incremental' => true,
                    ],
                    'duration_seconds' => rand(3, 60),  // Inc çok daha hızlı
                ];
            })(),
            'backup-restore-chain' => (function () use ($args): array {
                $chainPaths = (array) ($args['chain_paths'] ?? []);
                return [
                    'restored' => true,
                    'backup_id' => $args['backup_id'] ?? null,
                    'username' => $args['username'] ?? 'unknown',
                    'chain_length' => count($chainPaths),
                    'chain_paths_merged' => $chainPaths,
                    'files_restored' => rand(5_000, 50_000),
                    'databases_restored' => rand(0, 5),
                    'duration_seconds' => rand(30, 900),
                ];
            })(),
            'backup-list' => (function () use ($args): array {
                $username = $args['username'] ?? 'unknown';
                $samples = [];
                $count = rand(3, 5);
                for ($i = 0; $i < $count; $i++) {
                    $ts = now()->subDays($i)->format('Ymd-His');
                    $samples[] = [
                        'file_path' => "/var/backups/onoxsoft/{$username}/{$username}-full-{$ts}.tar.gz",
                        'size_bytes' => rand(50_000_000, 1_500_000_000),
                        'created_at' => now()->subDays($i)->toIso8601String(),
                        'sha256' => hash('sha256', $username.$ts),
                    ];
                }

                return ['username' => $username, 'count' => $count, 'backups' => $samples];
            })(),
            'backup-prune' => [
                'deleted' => true,
                'backup_id' => $args['backup_id'] ?? null,
                'backup_file' => $args['backup_file'] ?? null,
                'freed_bytes' => rand(50_000_000, 1_500_000_000),
            ],
            'backup-verify' => [
                'valid' => true,
                'backup_file' => $args['backup_file'] ?? null,
                'sha256_match' => true,
                'tar_integrity_ok' => true,
            ],

            // ── Snapshot (full-account capture) ──────────────────────────
            'snapshot-create' => (function () use ($args): array {
                $username = $args['username'] ?? 'unknown';
                $number = $args['snapshot_number'] ?? ('SNAP-'.now()->format('Y').'-'.str_pad((string) rand(1, 999999), 6, '0', STR_PAD_LEFT));
                $format = $args['format'] ?? 'onoxsoft';
                $prefix = $format === 'cpmove' ? 'cpmove' : 'onoxsoft-snap';
                $path = "/var/backups/onoxsoft/snapshots/{$prefix}-{$username}.tar.gz";
                $includes = (array) ($args['includes'] ?? []);

                $manifest = [
                    'snapshot_format' => $format === 'cpmove' ? 'cpanel-cpmove-v110' : 'onoxsoft-v1',
                    'snapshot_number' => $number,
                    'created_at' => now()->toIso8601String(),
                    'account' => ['username' => $username],
                ];

                if ($includes['files'] ?? true) {
                    $manifest['files'] = ['count' => rand(500, 25000), 'size_bytes' => rand(50_000_000, 800_000_000)];
                }
                if ($includes['databases'] ?? true) {
                    $manifest['databases'] = [['name' => $username.'_main', 'size_mb' => rand(5, 250)]];
                }
                if ($includes['dns'] ?? true) {
                    $manifest['dns'] = [['records' => rand(5, 30)]];
                }
                if ($includes['email'] ?? true) {
                    $manifest['email'] = ['accounts' => rand(0, 8)];
                }
                if ($includes['cron'] ?? true) {
                    $manifest['cron_jobs'] = rand(0, 5);
                }

                return [
                    'file_path' => $path,
                    'path' => $path,
                    'size_bytes' => rand(80_000_000, 1_200_000_000),
                    'sha256' => hash('sha256', $username.$number),
                    'checksum_sha256' => hash('sha256', $username.$number),
                    'manifest' => $manifest,
                    'duration_seconds' => rand(45, 300),
                ];
            })(),
            'snapshot-restore' => [
                'restored' => true,
                'target_username' => $args['target_username'] ?? 'unknown',
                'files_restored' => rand(500, 25000),
                'databases_restored' => rand(0, 5),
                'duration_seconds' => rand(60, 900),
            ],
            'snapshot-parse-manifest' => (function () use ($args): array {
                $sourcePanel = $args['source_panel'] ?? 'cpanel';
                $username = $args['expected_username'] ?? 'imported_user';

                $manifest = [
                    'snapshot_format' => $sourcePanel === 'plesk' ? 'plesk-export-v18' : 'cpanel-cpmove-v110',
                    'source_panel' => $sourcePanel,
                    'imported_at' => now()->toIso8601String(),
                    'account' => ['username' => $username],
                    'files' => ['count' => rand(1000, 15000), 'size_bytes' => rand(100_000_000, 500_000_000)],
                    'databases' => [['name' => $username.'_wp1', 'size_mb' => rand(20, 200)]],
                    'dns_zones' => [['records' => rand(8, 25)]],
                    'email_accounts' => rand(2, 12),
                    'cron_jobs' => rand(0, 5),
                ];

                return [
                    'manifest' => $manifest,
                    'sha256' => hash('sha256', $username.($args['file_path'] ?? '')),
                    'duration_seconds' => rand(5, 30),
                ];
            })(),
            'snapshot-prune' => [
                'deleted' => true,
                'snapshot_id' => $args['snapshot_id'] ?? null,
            ],
            'snapshot-verify' => [
                'valid' => true,
                'sha256_match' => true,
            ],

            // ── Htaccess rebuild ─────────────────────────────────────────
            'htaccess-rebuild' => (function () use ($args): array {
                $username = $args['username'] ?? 'unknown';
                $sections = (array) ($args['sections'] ?? []);
                // Mimic the bash script: a section is "written" only if non-empty.
                $written = [];
                foreach ($sections as $name => $value) {
                    if ($value === null) {
                        continue;
                    }
                    if (is_array($value) && $value === []) {
                        continue;
                    }
                    if (is_array($value) && array_is_list($value) === false) {
                        // Object-shaped section — heuristic: "enabled" must be true
                        // if present, otherwise treat as written when it has fields.
                        if (array_key_exists('enabled', $value) && ! $value['enabled']) {
                            continue;
                        }
                    }
                    $written[] = $name;
                }

                return [
                    'domain' => $args['domain'] ?? '',
                    'htaccess_path' => "/home/{$username}/public_html/.htaccess",
                    'size_bytes' => rand(1500, 5000),
                    'sections_written' => $written,
                    'backup_path' => "/home/{$username}/public_html/.htaccess.onx-bak-" . time(),
                    'apache_reload_required' => false,
                ];
            })(),

            // ── Apache global config (Faz L) ─────────────────────────────
            'apache-write-conf' => (function () use ($args): array {
                $target = (string) ($args['target'] ?? '/etc/httpd/conf.d/onox-custom.conf');

                return [
                    'ok' => true,
                    'path' => $target,
                    'backup' => $target . '.bak-' . time(),
                    'uptime' => now()->subMinutes(rand(30, 600))->toIso8601String(),
                ];
            })(),
            'apache-test' => [
                'syntax_ok' => true,
                'output' => 'Syntax OK',
                'exit_code' => 0,
            ],
            'apache-reload' => [
                'reloaded' => true,
                'uptime' => '5 days, 18 hours',
            ],
            'apache-restart' => [
                'restarted' => true,
                'uptime' => '0 seconds',
            ],
            'apache-module-toggle' => [
                'module' => $args['module'] ?? 'unknown',
                'enabled' => (bool) ($args['enable'] ?? true),
                'conf_path' => '/etc/httpd/conf.modules.d/onox-'.($args['module'] ?? 'unknown').'.conf',
                'reload_required' => true,
            ],
            'apache-module-install' => (function () use ($args): array {
                $mod = (string) ($args['module'] ?? 'mod_unknown');
                $pkg = (string) ($args['package'] ?? 'mod-unknown');
                return [
                    'module' => $mod,
                    'package' => $pkg,
                    'installed' => true,
                    'install_method' => 'dnf',
                    'duration_s' => random_int(15, 120),
                    'conf_path' => "/etc/httpd/conf.modules.d/00-{$mod}.conf",
                    'enabled_by_default' => false,
                ];
            })(),

            // Apache live data — production'da `apachectl -M` ve mod_status parse
            'apache-modules-list' => [
                'loaded' => [
                    ['name' => 'core_module',       'kind' => 'static',  'description' => 'Core Apache HTTPD'],
                    ['name' => 'mpm_event_module',  'kind' => 'static',  'description' => 'Event MPM'],
                    ['name' => 'authz_core_module', 'kind' => 'shared',  'description' => 'Core authorization'],
                    ['name' => 'rewrite_module',    'kind' => 'shared',  'description' => 'mod_rewrite'],
                    ['name' => 'ssl_module',        'kind' => 'shared',  'description' => 'SSL/TLS'],
                    ['name' => 'proxy_module',      'kind' => 'shared',  'description' => 'Reverse proxy'],
                    ['name' => 'proxy_fcgi_module', 'kind' => 'shared',  'description' => 'FastCGI proxy (PHP-FPM)'],
                    ['name' => 'security2_module',  'kind' => 'shared',  'description' => 'ModSecurity 2.x'],
                    ['name' => 'http2_module',      'kind' => 'shared',  'description' => 'HTTP/2 support'],
                    ['name' => 'headers_module',    'kind' => 'shared',  'description' => 'Response header manipulation'],
                    ['name' => 'deflate_module',    'kind' => 'shared',  'description' => 'Gzip compression'],
                    ['name' => 'brotli_module',     'kind' => 'shared',  'description' => 'Brotli compression'],
                    ['name' => 'expires_module',    'kind' => 'shared',  'description' => 'Cache-control headers'],
                ],
                'count' => 13,
            ],
            'apache-status' => [
                'service' => 'active',
                'uptime' => '5 days, 18 hours',
                'version' => 'Apache/2.4.62',
                'workers_busy' => rand(2, 12),
                'workers_idle' => rand(5, 25),
                'requests_per_sec' => round(rand(50, 500) / 10, 1),
                'bytes_per_sec' => rand(1024 * 50, 1024 * 1024),
                'total_accesses' => rand(100_000, 5_000_000),
                'cpu_load' => round(rand(5, 80) / 100, 2),
            ],

            // Dovecot live
            'dovecot-status' => [
                'service' => 'active',
                'uptime' => '5 days, 18 hours',
                'version' => 'Dovecot 2.3.21',
                'pid' => 3017,
                'config_ok' => true,
            ],
            'dovecot-sessions' => (function (): array {
                // Production: `doveadm who` parse; dev'de gerçekçi placeholder yok — empty
                return [
                    'sessions' => [],
                    'total' => 0,
                    'note' => 'doveadm who: production-only — dev/Windows boş döner',
                ];
            })(),

            // MariaDB live status
            'db-server-status' => [
                'service' => 'active',
                'version' => 'MariaDB 10.11.6',
                'uptime_seconds' => rand(3600, 2_592_000),
                'connections' => rand(5, 50),
                'max_connections' => 150,
                'threads_running' => rand(1, 10),
                'queries_per_second' => round(rand(20, 500) / 10, 1),
                'slow_queries' => rand(0, 50),
                'innodb_buffer_pool_size_mb' => 512,
                'innodb_buffer_pool_used_mb' => rand(100, 500),
                'data_size_mb' => rand(50, 2048),
                'index_size_mb' => rand(10, 512),
            ],

            // ── Log tail (Faz L) ─────────────────────────────────────────
            'log-tail' => (function () use ($args): array {
                $path = (string) ($args['path'] ?? '/var/log/httpd/error_log');
                $lines = (int) ($args['lines'] ?? 200);
                $filter = (string) ($args['filter'] ?? '');

                $samples = [
                    'apache_error' => [
                        ['error', '[client 192.168.1.10:54321] AH02032: Hostname provided via SNI'],
                        ['warn',  '[mpm_event:notice] AH00489: Apache/2.4.62 configured'],
                        ['error', 'File does not exist: /var/www/html/.git/HEAD'],
                        ['error', 'AH01797: client denied by server configuration'],
                        ['notice', 'AH00094: Command line: /usr/sbin/httpd'],
                    ],
                    'apache_access' => [
                        ['info', '192.168.1.10 - - [16/May/2026:11:23:01 +0300] "GET /api/v1/health HTTP/1.1" 200 47'],
                        ['info', '10.0.0.5 - - [16/May/2026:11:23:02 +0300] "POST /login HTTP/2" 302 0'],
                    ],
                    'php_fpm' => [
                        ['warn', 'PHP Notice: Undefined index "user_id" in /home/onx_acme01/public_html/index.php:24'],
                        ['error', 'PHP Fatal error: Allowed memory size of 134217728 bytes exhausted'],
                    ],
                    // Per-user PHP error log (FPM pool php_admin_value[error_log])
                    'php_errors' => [
                        ['error',      'PHP Fatal error:  Uncaught Error: Call to undefined function bar() in /home/onx_demo01/public_html/wp-content/themes/site/functions.php:128'],
                        ['error',      'PHP Fatal error:  Allowed memory size of 268435456 bytes exhausted (tried to allocate 20480 bytes) in /home/onx_demo01/public_html/wp-includes/class-wp-query.php on line 4012'],
                        ['error',      'PHP Fatal error:  Maximum execution time of 300 seconds exceeded in /home/onx_demo01/public_html/wp-cron.php on line 87'],
                        ['warning',    'PHP Warning:  Undefined variable $foo in /home/onx_demo01/public_html/index.php on line 42'],
                        ['warning',    'PHP Warning:  fopen(/tmp/cache.lock): Failed to open stream: Permission denied in /home/onx_demo01/public_html/inc/cache.php on line 18'],
                        ['warning',    'PHP Warning:  count(): Parameter must be an array or an object that implements Countable in /home/onx_demo01/public_html/wp-content/plugins/foo/foo.php on line 215'],
                        ['warning',    'PHP Warning:  Trying to access array offset on value of type null in /home/onx_demo01/public_html/wp-content/plugins/bar/bar.php on line 71'],
                        ['notice',     'PHP Notice:  Undefined index: id in /home/onx_demo01/public_html/api/v1/user.php on line 33'],
                        ['notice',     'PHP Notice:  Trying to get property of non-object in /home/onx_demo01/public_html/include/render.php on line 88'],
                        ['notice',     'PHP Notice:  Constant FOO already defined in /home/onx_demo01/public_html/config.php on line 12'],
                        ['deprecated', 'PHP Deprecated:  Required parameter $bar follows optional parameter $foo in /home/onx_demo01/public_html/lib/helper.php on line 55'],
                        ['deprecated', 'PHP Deprecated:  Function utf8_encode() is deprecated in /home/onx_demo01/public_html/inc/sanitize.php on line 27'],
                        ['deprecated', 'PHP Deprecated:  Implicit conversion from float to int loses precision in /home/onx_demo01/public_html/inc/math.php on line 14'],
                        ['parse',      "PHP Parse error:  syntax error, unexpected token \";\" in /home/onx_demo01/public_html/wp-content/themes/site/header.php on line 18"],
                        ['parse',      "PHP Parse error:  Unclosed '{' in /home/onx_demo01/public_html/inc/router.php on line 240"],
                    ],
                    'mariadb' => [
                        ['warn', '[Warning] InnoDB: Difficult to find free blocks in the buffer pool'],
                        ['info', '[Note] InnoDB: Buffer pool(s) load completed'],
                    ],
                    'maillog' => [
                        ['info', 'postfix/smtpd[12345]: connect from unknown[192.0.2.50]'],
                        ['warn', 'postfix/smtpd[12345]: NOQUEUE: reject: RCPT from spam-host: 554 5.7.1'],
                    ],
                    'secure' => [
                        ['error', 'sshd[12345]: Failed password for root from 203.0.113.42 port 54321'],
                        ['notice', 'sshd[12346]: Accepted publickey for root from 192.0.2.10'],
                    ],
                    'fail2ban' => [
                        ['notice', '[sshd] Ban 203.0.113.42'],
                        ['info', '[sshd] Found 203.0.113.50 - 5 attempts'],
                    ],
                ];

                // Pick a bucket by path keyword
                // /home/<user>/logs/php_errors.log → per-user customer PHP error log
                $bucket = $samples['apache_error'];
                if (str_contains($path, 'php_errors') || preg_match('#/home/[^/]+/logs/#', $path)) {
                    $bucket = $samples['php_errors'];
                } else {
                    foreach (['fail2ban', 'maillog', 'mariadb', 'php', 'access', 'error', 'secure'] as $kw) {
                        if (str_contains($path, $kw)) {
                            $key = match (true) {
                                str_contains($kw, 'access') => 'apache_access',
                                str_contains($kw, 'error') => 'apache_error',
                                str_contains($kw, 'php') => 'php_fpm',
                                default => $kw,
                            };
                            $bucket = $samples[$key] ?? $samples['apache_error'];
                            break;
                        }
                    }
                }

                $rows = collect(range(1, min($lines, 200)))
                    ->map(function ($i) use ($bucket) {
                        $entry = $bucket[array_rand($bucket)];

                        return [
                            'time' => now()->subSeconds($i * 30)->format('Y-m-d H:i:s'),
                            'level' => $entry[0],
                            'message' => $entry[1],
                        ];
                    })
                    ->when($filter !== '', fn ($c) => $c->filter(
                        fn ($r) => stripos($r['message'], $filter) !== false
                    ))
                    ->values()
                    ->all();

                return [
                    'path' => $path,
                    'lines' => $rows,
                    'count' => count($rows),
                    'size_bytes' => count($rows) * 180,
                    'mtime' => now()->subSeconds(rand(5, 600))->toIso8601String(),
                    'exists' => true,
                ];
            })(),

            // ── Log clear (truncate to 0 bytes) ──────────────────────────
            // Args: { path: string }
            // Bash tarafı: : > "$path" (path whitelist sonrası)
            'log-clear' => [
                'path' => (string) ($args['path'] ?? ''),
                'truncated' => true,
                'previous_size_bytes' => rand(1024, 524_288),
                'new_size_bytes' => 0,
            ],

            // ── Cron provisioner ─────────────────────────────────────────
            'cron-write' => (function () use ($args): array {
                $username = $args['username'] ?? 'unknown';
                $jobs = (array) ($args['cron_jobs'] ?? []);

                return [
                    'crontab_path' => "/etc/cron.d/{$username}",
                    'jobs_count' => count($jobs),
                    'bytes_written' => max(64, count($jobs) * 120),
                    'username' => $username,
                ];
            })(),
            'cron-remove' => [
                'removed' => true,
                'crontab_path' => '/etc/cron.d/'.($args['username'] ?? 'unknown'),
            ],
            'cron-list' => [
                'username' => $args['username'] ?? 'unknown',
                'jobs' => [],
            ],

            // ── Backup offsite destinations ──────────────────────────────
            'backup-destination-test' => (function () use ($args): array {
                $driver = (string) ($args['type'] ?? $args['driver'] ?? 'local');

                // Driver-specific realistic step set
                $authMsg = match ($driver) {
                    's3', 'wasabi' => 'AWS Signature V4 ile imza doğrulandı',
                    'backblaze_b2' => 'B2 Application Key kabul edildi',
                    'sftp' => 'SSH handshake + key auth başarılı',
                    'ftp' => 'FTP login (PASS) başarılı',
                    'restic' => 'Restic repository unlock başarılı',
                    default => 'Local dizin erişilebilir (read+write)',
                };
                $listMsg = match ($driver) {
                    's3', 'wasabi', 'backblaze_b2' => 'Bucket erişilebilir, prefix listelendi',
                    'sftp', 'ftp' => 'Remote dizin listelendi',
                    'restic' => 'Snapshot listesi alındı',
                    default => 'Dizin içeriği listelendi',
                };

                // Realistic throughput by driver type
                [$upMin, $upMax, $dnMin, $dnMax] = match ($driver) {
                    'local' => [180, 420, 220, 480],          // disk speed
                    's3', 'wasabi' => [25, 90, 40, 120],      // cloud, fast
                    'backblaze_b2' => [15, 60, 25, 80],
                    'sftp' => [8, 45, 10, 55],
                    'ftp' => [5, 30, 8, 35],
                    'restic' => [12, 50, 18, 60],
                    default => [10, 40, 12, 45],
                };
                $upload = round(random_int($upMin * 10, $upMax * 10) / 10, 1);
                $download = round(random_int($dnMin * 10, $dnMax * 10) / 10, 1);
                $sampleSize = 10; // MB
                $sampleBytes = $sampleSize * 1024 * 1024;

                $authDur = random_int(40, 280);
                $listDur = random_int(60, 320);
                $writeDur = (int) round(($sampleSize / max($upload, 0.1)) * 1000);
                $readDur = (int) round(($sampleSize / max($download, 0.1)) * 1000);
                $shaDur = random_int(15, 60);
                $delDur = random_int(20, 120);
                $totalDur = $authDur + $listDur + $writeDur + $readDur + $shaDur + $delDur;

                // Free space realistic by driver
                $freeSpace = match ($driver) {
                    'local' => random_int(80, 900) * 1024 * 1024 * 1024,           // 80-900 GB
                    's3', 'wasabi', 'backblaze_b2' => null,                        // unlimited cloud
                    default => random_int(50, 500) * 1024 * 1024 * 1024,
                };

                return [
                    'ok' => true,
                    'driver' => $driver,
                    'reachable' => true,
                    'writeable' => true,
                    'latency_ms' => $authDur,
                    'free_space_bytes' => $freeSpace,
                    'free_space_mb' => $freeSpace !== null ? (int) ($freeSpace / 1024 / 1024) : null,
                    'steps' => [
                        ['name' => 'auth', 'ok' => true, 'duration_ms' => $authDur, 'message' => $authMsg],
                        ['name' => 'list_bucket', 'ok' => true, 'duration_ms' => $listDur, 'message' => $listMsg],
                        ['name' => 'write_test_file', 'ok' => true, 'duration_ms' => $writeDur, 'message' => "{$sampleSize} MB test dosyası yazıldı"],
                        ['name' => 'read_test_file', 'ok' => true, 'duration_ms' => $readDur, 'message' => "{$sampleSize} MB test dosyası okundu"],
                        ['name' => 'sha256_verify', 'ok' => true, 'duration_ms' => $shaDur, 'message' => 'SHA-256 doğrulandı, bit error yok'],
                        ['name' => 'delete_test_file', 'ok' => true, 'duration_ms' => $delDur, 'message' => 'Test dosyası temizlendi'],
                    ],
                    'throughput' => [
                        'upload_mbps' => $upload,
                        'download_mbps' => $download,
                        'sample_size_mb' => $sampleSize,
                        'sample_bytes' => $sampleBytes,
                    ],
                    'total_duration_ms' => $totalDur,
                    'tested_at' => now()->toIso8601String(),
                ];
            })(),
            'backup-destination-list' => [
                'files' => [],
            ],
            'backup-destination-delete' => [
                'deleted' => true,
            ],
            'backup-push-s3', 'backup-push-b2', 'backup-push-sftp', 'backup-push-ftp' => [
                'uploaded' => true,
                'remote_path' => ltrim($args['local_path'] ?? 'backup.tar.gz', '/'),
                'bytes' => rand(50_000_000, 500_000_000),
                'duration_seconds' => rand(5, 60),
            ],
            'backup-pull-from-destination' => [
                'downloaded' => true,
                'local_path' => $args['local_path'] ?? '/tmp/restore.tar.gz',
                'bytes' => rand(50_000_000, 500_000_000),
            ],
            // ── FPM Pool tuning (Phase 2) ────────────────────────────────────
            'fpm-pool-tune' => (function () use ($args): array {
                $user = (string) ($args['username'] ?? 'unknown');
                $ver = (string) ($args['php_version'] ?? '8.2');
                $vNoDot = str_replace('.', '', $ver);

                return [
                    'username' => $user,
                    'php_version' => $ver,
                    'pool_file' => "/etc/php-fpm.d/{$user}_php{$vNoDot}.conf",
                    'pm_mode' => $args['pm_mode'] ?? 'ondemand',
                    'max_children' => (int) ($args['max_children'] ?? 10),
                    'reloaded' => true,
                    'written_at' => now()->toIso8601String(),
                ];
            })(),
            'fpm-pool-status' => (function () use ($args): array {
                $user = (string) ($args['username'] ?? 'unknown');
                // Mock canlı pool durumu — pm.status path benzeri çıktı
                $maxChildren = (int) ($args['max_children'] ?? 10);
                $active = random_int(0, max(1, (int) ($maxChildren / 2)));
                $idle = max(0, $maxChildren - $active);

                return [
                    'username' => $user,
                    'pool' => $user,
                    'process_manager' => $args['pm_mode'] ?? 'ondemand',
                    'start_time' => now()->subHours(random_int(1, 240))->toIso8601String(),
                    'accepted_conn' => random_int(1000, 1_500_000),
                    'listen_queue' => 0,
                    'max_listen_queue' => 0,
                    'idle_processes' => $idle,
                    'active_processes' => $active,
                    'total_processes' => $maxChildren,
                    'max_active_processes' => $active,
                    'max_children_reached' => 0,
                    'slow_requests' => random_int(0, 5),
                ];
            })(),
            'fpm-reload' => [
                'php_version' => $args['php_version'] ?? '8.2',
                'reloaded' => true,
                'reloaded_at' => now()->toIso8601String(),
            ],
            // ── PHP version registry ────────────────────────────────────────
            'php-list-installed' => [
                'versions' => [
                    ['version' => '7.4', 'binary' => '/usr/bin/php74', 'fpm_unit' => 'php74-php-fpm', 'installed' => true],
                    ['version' => '8.0', 'binary' => '/usr/bin/php80', 'fpm_unit' => 'php80-php-fpm', 'installed' => false],
                    ['version' => '8.1', 'binary' => '/usr/bin/php81', 'fpm_unit' => 'php81-php-fpm', 'installed' => true],
                    ['version' => '8.2', 'binary' => '/usr/bin/php82', 'fpm_unit' => 'php82-php-fpm', 'installed' => true],
                    ['version' => '8.3', 'binary' => '/usr/bin/php83', 'fpm_unit' => 'php83-php-fpm', 'installed' => true],
                ],
                'default' => '8.2',
            ],
            'php-list-extensions' => [
                'version' => $args['version'] ?? '8.2',
                'extensions' => ['core', 'mysqli', 'pdo', 'pdo_mysql', 'curl', 'gd', 'mbstring', 'xml', 'zip', 'opcache', 'intl', 'bcmath', 'imagick'],
            ],
            // ── Faz R: PHP install / uninstall / extension manager ──────────
            'php-install' => (function () use ($args): array {
                $version = (string) ($args['version'] ?? '8.3');
                $source = (string) ($args['source'] ?? 'remi');
                $vNoDot = str_replace('.', '', $version);

                return [
                    'installed' => true,
                    'version' => $version,
                    'source' => $source,
                    'runtime' => "PHP {$version}.13 (cli) (built: ".date('M j Y H:i:s').") (NTS)",
                    'fpm_unit' => "php{$vNoDot}-php-fpm",
                    'binary' => "/usr/bin/php{$vNoDot}",
                    'extensions_installed' => [
                        'core', 'curl', 'date', 'fileinfo', 'filter', 'gd', 'hash', 'iconv',
                        'intl', 'json', 'mbstring', 'mysqli', 'mysqlnd', 'opcache', 'openssl',
                        'pcre', 'pdo', 'pdo_mysql', 'phar', 'reflection', 'session',
                        'simplexml', 'soap', 'sodium', 'spl', 'standard', 'tokenizer',
                        'xml', 'xmlreader', 'xmlwriter', 'zip', 'zlib',
                    ],
                    'already_present' => false,
                ];
            })(),
            'php-uninstall' => (function () use ($args): array {
                $version = (string) ($args['version'] ?? '7.4');
                $vNoDot = str_replace('.', '', $version);

                return [
                    'uninstalled' => true,
                    'version' => $version,
                    'packages_removed' => rand(8, 18),
                    'vhosts_using' => 0,
                    'fpm_unit' => "php{$vNoDot}-php-fpm",
                ];
            })(),
            'php-ext-install' => (function () use ($args): array {
                $version = (string) ($args['version'] ?? '8.3');
                $ext = (string) ($args['extension'] ?? 'imagick');
                $vNoDot = str_replace('.', '', $version);

                return [
                    'installed' => true,
                    'version' => $version,
                    'extension' => $ext,
                    'package' => "php{$vNoDot}-php-{$ext}",
                    'runtime_active' => true,
                    'fpm_unit' => "php{$vNoDot}-php-fpm",
                ];
            })(),
            'php-ext-uninstall' => (function () use ($args): array {
                $version = (string) ($args['version'] ?? '8.3');
                $ext = (string) ($args['extension'] ?? 'redis');
                $vNoDot = str_replace('.', '', $version);

                return [
                    'uninstalled' => true,
                    'version' => $version,
                    'extension' => $ext,
                    'package' => "php{$vNoDot}-php-{$ext}",
                ];
            })(),
            'php-ext-list' => (function () use ($args): array {
                $version = (string) ($args['version'] ?? '8.3');
                $vNoDot = str_replace('.', '', $version);
                $installed = [
                    'core', 'ctype', 'curl', 'date', 'dom', 'fileinfo', 'filter', 'gd',
                    'hash', 'iconv', 'intl', 'json', 'libxml', 'mbstring', 'mysqli',
                    'mysqlnd', 'opcache', 'openssl', 'pcre', 'pdo', 'pdo_mysql', 'phar',
                    'reflection', 'session', 'simplexml', 'soap', 'sodium', 'spl',
                    'standard', 'tokenizer', 'xml', 'xmlreader', 'xmlwriter', 'zip', 'zlib',
                ];
                $available = [
                    'apcu', 'bcmath', 'enchant', 'exif', 'ftp', 'gettext', 'gmp',
                    'imagick', 'imap', 'ldap', 'memcached', 'mongodb', 'pdo_pgsql',
                    'pdo_sqlite', 'pgsql', 'pspell', 'redis', 'snmp', 'sqlite3',
                    'sysvmsg', 'sysvsem', 'sysvshm', 'tidy', 'xdebug', 'xsl', 'yaml',
                ];

                return [
                    'version' => $version,
                    'binary' => "/usr/bin/php{$vNoDot}",
                    'installed' => $installed,
                    'available' => $available,
                    'all_known' => array_values(array_unique(array_merge($installed, $available))),
                ];
            })(),
            // ── System update / version check (Phase D) ──────────────────────
            'check-updates' => [
                'count' => 3,
                'packages' => [
                    ['name' => 'httpd', 'current' => '2.4.62-1.el9', 'available' => '2.4.63-1.el9', 'size_kb' => 1420],
                    ['name' => 'mariadb-server', 'current' => '10.11.6-1', 'available' => '10.11.7-1', 'size_kb' => 21100],
                    ['name' => 'php82-php-fpm', 'current' => '8.2.18-1', 'available' => '8.2.21-1', 'size_kb' => 1150],
                ],
                'reboot_required' => false,
                'checked_at' => now()->toIso8601String(),
            ],
            'system-info-collect' => [
                'cpu_model' => 'Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz',
                'cpu_cores' => 8,
                'kernel' => '5.14.0-503.el9.x86_64',
                'distro' => 'AlmaLinux 9.4',
                'memory_total_bytes' => 16 * 1024 * 1024 * 1024,
                'hostname' => 'panel.demo.local',
            ],
            // ── Service lifecycle (graceful reload + enable/disable) ─────────
            'service-reload' => [
                'unit' => $args['unit'] ?? 'unknown',
                'action' => 'reload',
                'success' => true,
                'message' => ($args['unit'] ?? 'unknown') . ' graceful reloaded (mock)',
            ],
            'service-enable' => [
                'unit' => $args['unit'] ?? 'unknown',
                'action' => 'enable',
                'success' => true,
                'message' => ($args['unit'] ?? 'unknown') . ' enable edildi (mock)',
            ],
            'service-disable' => [
                'unit' => $args['unit'] ?? 'unknown',
                'action' => 'disable',
                'success' => true,
                'message' => ($args['unit'] ?? 'unknown') . ' disable edildi (mock)',
            ],
            'service-restart' => [
                'unit' => $args['unit'] ?? 'unknown',
                'action' => 'restart',
                'success' => true,
                'message' => ($args['unit'] ?? 'unknown') . ' yeniden başlatıldı (mock)',
            ],
            // ── Mailbox provisioning (mock) ──────────────────────────────────
            'mailbox-create' => (function () use ($args): array {
                $email = $args['email'] ?? (($args['local'] ?? 'user') . '@' . ($args['domain'] ?? 'example.com'));
                $quotaMb = (int) ($args['quota_mb'] ?? 1024);
                $quotaBytes = $quotaMb * 1024 * 1024;
                // SHA512-CRYPT mock format: $6$<16 char salt>$<86 char hash>
                $salt = substr(bin2hex(random_bytes(8)), 0, 16);
                $hash = substr(hash('sha512', $email . $salt), 0, 86);

                return [
                    'email' => $email,
                    'local' => $args['local'] ?? explode('@', $email)[0],
                    'domain' => $args['domain'] ?? (explode('@', $email)[1] ?? 'example.com'),
                    'password_hash' => "{SHA512-CRYPT}\$6\${$salt}\${$hash}",
                    'maildir' => '/var/vmail/' . ($args['domain'] ?? 'example.com') . '/' . ($args['local'] ?? 'unknown') . '/Maildir',
                    'home' => $args['home'] ?? '/var/vmail/' . ($args['domain'] ?? 'example.com') . '/' . ($args['local'] ?? 'unknown'),
                    'uid' => (int) ($args['uid'] ?? 5000),
                    'gid' => (int) ($args['gid'] ?? 5000),
                    'quota_mb' => $quotaMb,
                    'quota_bytes' => $quotaBytes,
                    'created' => true,
                ];
            })(),
            'mailbox-remove' => [
                'email' => $args['email'] ?? 'unknown',
                'data_deleted' => (bool) ($args['delete_data'] ?? false),
                'removed' => true,
            ],
            'mailbox-password' => (function () use ($args): array {
                $email = $args['email'] ?? 'unknown';
                $salt = substr(bin2hex(random_bytes(8)), 0, 16);
                $hash = substr(hash('sha512', $email . microtime()), 0, 86);

                return [
                    'email' => $email,
                    'password_hash' => "{SHA512-CRYPT}\$6\${$salt}\${$hash}",
                    'updated' => true,
                ];
            })(),
            'mailbox-quota' => [
                'email' => $args['email'] ?? 'unknown',
                'quota_mb' => (int) ($args['quota_mb'] ?? 1024),
                'quota_bytes' => (int) ($args['quota_mb'] ?? 1024) * 1024 * 1024,
                'updated' => true,
            ],
            'mailbox-expunge-trash' => (function () use ($args): array {
                return [
                    'email' => $args['email'] ?? 'unknown',
                    'mailbox' => 'Trash',
                    'expunged_messages' => random_int(0, 200),
                    'freed_bytes' => random_int(0, 50_000_000),
                    'duration_seconds' => random_int(1, 30),
                ];
            })(),
            'mailbox-compact' => (function () use ($args): array {
                return [
                    'email' => $args['email'] ?? 'unknown',
                    'mailbox' => $args['mailbox'] ?? 'INBOX',
                    'cache_purged' => true,
                    'index_optimized' => true,
                    'reclaimed_bytes' => random_int(1_000_000, 50_000_000),
                    'duration_seconds' => random_int(2, 60),
                ];
            })(),
            'mailbox-disk-usage' => (function () use ($args): array {
                $quotaMb = (int) ($args['quota_mb'] ?? 1024);
                $maxBytes = max($quotaMb * 1024 * 1024, 1024 * 1024); // en az 1 MiB
                $bytes = rand(0, $maxBytes);
                $messages = rand(50, 500);
                $newBytes = (int) ($bytes * 0.1);

                return [
                    'email' => $args['email'] ?? 'unknown',
                    'bytes' => $bytes,
                    'messages' => $messages,
                    'cur_bytes' => $bytes - $newBytes,
                    'new_bytes' => $newBytes,
                ];
            })(),
            'mailbox-list' => (function () use ($args): array {
                $domain = $args['domain'] ?? 'example.com';
                $samples = [
                    ['local' => 'info', 'quota_mb' => 1024, 'usage_bytes' => 134_217_728, 'messages' => 312, 'enabled' => true],
                    ['local' => 'contact', 'quota_mb' => 2048, 'usage_bytes' => 268_435_456, 'messages' => 845, 'enabled' => true],
                    ['local' => 'sales', 'quota_mb' => 5120, 'usage_bytes' => 1_073_741_824, 'messages' => 1923, 'enabled' => true],
                    ['local' => 'noreply', 'quota_mb' => 256, 'usage_bytes' => 12_582_912, 'messages' => 28, 'enabled' => false],
                ];

                $mailboxes = array_map(function (array $sample) use ($domain): array {
                    return [
                        ...$sample,
                        'email' => $sample['local'] . '@' . $domain,
                        'domain' => $domain,
                        'maildir' => "/var/vmail/{$domain}/{$sample['local']}/Maildir",
                    ];
                }, $samples);

                return [
                    'domain' => $domain,
                    'count' => count($mailboxes),
                    'mailboxes' => $mailboxes,
                ];
            })(),
            // ── SSH self-service (authorized_keys) ───────────────────────────
            'ssh-key-add' => (function () use ($args): array {
                $key = (string) ($args['key'] ?? '');
                $type = $key !== '' ? (explode(' ', $key, 2)[0] ?: 'ssh-ed25519') : 'ssh-ed25519';
                $fp = 'SHA256:'.rtrim(strtr(base64_encode(hash('sha256', $key, true)), '+/', '-_'), '=');

                return [
                    'username' => $args['username'] ?? 'unknown',
                    'fingerprint' => $fp,
                    'key_type' => in_array($type, ['ssh-rsa', 'ssh-ed25519', 'ssh-ecdsa', 'sk-ssh-ed25519'], true) ? $type : 'ssh-ed25519',
                    'total_keys' => rand(1, 5),
                    'added' => true,
                ];
            })(),
            'ssh-key-remove' => [
                'username' => $args['username'] ?? 'unknown',
                'removed' => true,
                'remaining_keys' => rand(0, 4),
            ],
            'ssh-key-list' => (function () use ($args): array {
                $username = $args['username'] ?? 'unknown';
                $samples = [
                    ['type' => 'ssh-ed25519', 'bits' => 256, 'comment' => 'work-laptop@mac'],
                    ['type' => 'ssh-rsa', 'bits' => 3072, 'comment' => 'home-desktop'],
                    ['type' => 'ssh-ed25519', 'bits' => 256, 'comment' => 'mobile-key'],
                ];
                $keys = [];
                $count = rand(1, 3);
                for ($i = 0; $i < $count; $i++) {
                    $s = $samples[$i];
                    $keys[] = [
                        'type' => $s['type'],
                        'fingerprint' => 'SHA256:'.substr(hash('sha256', $username.$i), 0, 43),
                        'comment' => $s['comment'],
                        'bits' => $s['bits'],
                        'line_no' => $i + 1,
                    ];
                }

                return [
                    'username' => $username,
                    'count' => $count,
                    'keys' => $keys,
                ];
            })(),

            // ── ModSecurity (per-domain overlay + status) ────────────────────
            'modsec-rule-write' => (function () use ($args): array {
                $domain = $args['domain'] ?? 'example.com';
                $rulesWritten = count((array) ($args['custom_rules'] ?? []))
                    + count((array) ($args['excluded_rules'] ?? []));

                return [
                    'domain' => $domain,
                    'conf_path' => "/etc/httpd/conf.d/modsec-onx-{$domain}.conf",
                    'engine' => $args['global_engine'] ?? (($args['enabled'] ?? true) ? 'On' : 'Off'),
                    'rules_written' => $rulesWritten,
                    'reloaded' => true,
                    'reload_required' => false,
                    'applied' => true,
                ];
            })(),
            'modsec-status' => (function () use ($args): array {
                $sampleIps = ['185.234.218.42', '91.108.4.10', '203.0.113.7', '45.33.32.156'];
                $topIps = [];
                foreach (array_slice($sampleIps, 0, rand(2, 4)) as $ip) {
                    $topIps[] = ['ip' => $ip, 'hits' => rand(3, 60)];
                }
                $topRules = [];
                foreach (['949110', '981318', '932100', '942100'] as $rid) {
                    $topRules[] = ['rule_id' => $rid, 'hits' => rand(1, 50)];
                }

                // Recent hits — gerçekçi sample data (production'da modsec_audit.log parse)
                $reasons = [
                    'SQL injection attempt', 'XSS attack detected',
                    'Remote code execution pattern', 'Path traversal attempt',
                    'Suspicious User-Agent', 'Scanner/bot detected',
                    'CSRF token missing', 'Command injection pattern',
                ];
                $uris = ['/wp-login.php', '/admin', '/wp-admin/admin-ajax.php', '/xmlrpc.php', '/phpmyadmin/', '/.env'];
                $recentHits = [];
                for ($i = 0; $i < 10; $i++) {
                    $recentHits[] = [
                        'rule_id' => (string) rand(920000, 980000),
                        'ip' => $sampleIps[array_rand($sampleIps)],
                        'uri' => $uris[array_rand($uris)],
                        'reason' => $reasons[array_rand($reasons)],
                        'hit_at' => now()->subMinutes(rand(1, 1440))->toIso8601String(),
                    ];
                }
                usort($recentHits, fn ($a, $b) => strcmp($b['hit_at'], $a['hit_at']));

                return [
                    'loaded' => true,
                    'version' => '2.9.6',
                    'engine_default' => 'On',
                    'log_file' => '/var/log/modsec_audit.log',
                    'window_h' => 24,
                    'blocks_24h' => rand(0, 100),
                    'top_blocked_ips' => $topIps,
                    'top_rules' => $topRules,
                    'recent_hits' => $recentHits,
                    'recent' => $recentHits,    // backward-compat
                ];
            })(),
            'modsec-enable' => ['enabled' => true, 'domain' => $args['domain'] ?? 'unknown', 'reloaded' => true],
            'modsec-disable' => ['enabled' => false, 'domain' => $args['domain'] ?? 'unknown', 'reloaded' => true],
            'modsec-rule-write' => [
                'applied' => true,
                'rule_id' => $args['rule_id'] ?? '',
                'action' => $args['action'] ?? 'exclude',
                'file_written' => '/etc/httpd/modsecurity.d/onox-overrides.conf',
            ],

            // ── ModSecurity — Faz Ü gelişmiş ──────────────────────────────────
            'modsec-rules-list' => (function (): array {
                // CRS rule catalog mock — realistic 9XX/9XX,4XX rule range
                $categories = ['SQL Injection', 'XSS', 'LFI', 'RFI', 'RCE', 'PHP Injection',
                               'Session Fixation', 'Generic', 'HTTP Compliance', 'Bot Detection'];
                $rules = [];
                $idBase = 920000;
                for ($i = 0; $i < 50; $i++) {
                    $cat = $categories[array_rand($categories)];
                    $rules[] = [
                        'rule_id' => (string) ($idBase + $i * rand(10, 100)),
                        'category' => $cat,
                        'tag' => 'attack-'.strtolower(explode(' ', $cat)[0]),
                        'severity' => ['CRITICAL', 'ERROR', 'WARNING'][rand(0, 2)],
                        'description' => $cat.' attack pattern #'.$i,
                        'phase' => 2,
                        'enabled' => (bool) rand(0, 1) ? true : true,
                    ];
                }
                return ['rules' => $rules, 'total' => count($rules)];
            })(),

            'modsec-audit-search' => (function () use ($args): array {
                $limit = (int) ($args['limit'] ?? 100);
                $domains = ['example.com', 'shop.example.com', 'blog.example.com', 'panel.onox.com.tr'];
                $uris = ['/wp-login.php', '/admin', '/wp-admin/admin-ajax.php', '/xmlrpc.php', '/.env', '/api/users'];
                $reasons = ['SQL injection (CRS 942100)', 'XSS (CRS 941100)', 'RCE pattern (CRS 932100)',
                            'Path traversal (CRS 930100)', 'LFI (CRS 930120)', 'Bot detected'];
                $methods = ['GET', 'POST', 'PUT'];
                $countries = ['CN', 'RU', 'BR', 'IN', 'VN', 'KP', 'IR', 'TR', 'US'];

                $events = [];
                for ($i = 0; $i < min($limit, 200); $i++) {
                    $events[] = [
                        'event_id' => 'mock-'.bin2hex(random_bytes(8)),
                        'source_ip' => long2ip(rand(1, 0xFFFFFFFF)),
                        'country_code' => $countries[array_rand($countries)],
                        'domain_name' => $domains[array_rand($domains)],
                        'uri' => $uris[array_rand($uris)],
                        'method' => $methods[array_rand($methods)],
                        'rule_id' => (string) rand(920000, 980000),
                        'rule_tag' => 'attack-'.['sqli', 'xss', 'rce', 'lfi'][rand(0, 3)],
                        'severity' => ['CRITICAL', 'ERROR', 'WARNING'][rand(0, 2)],
                        'reason' => $reasons[array_rand($reasons)],
                        'action_taken' => rand(0, 9) > 1 ? 'blocked' : 'passed',
                        'response_code' => rand(0, 9) > 1 ? 403 : 200,
                        'matched_data' => ['param' => 'user', 'value' => "' OR 1=1--"],
                        'event_at' => now()->subMinutes(rand(0, 1440))->toDateTimeString(),
                    ];
                }
                return ['events' => $events, 'total' => count($events)];
            })(),

            'modsec-whitelist-write' => [
                'applied' => true,
                'count' => count($args['ips'] ?? []),
                'file_written' => '/etc/httpd/modsecurity.d/onox-whitelist.conf',
                'reloaded' => true,
            ],

            'modsec-custom-rule-write' => [
                'applied' => true,
                'rule_id' => $args['rule_id'] ?? 0,
                'name' => $args['name'] ?? '',
                'file_written' => '/etc/httpd/modsecurity.d/onox-custom.conf',
                'syntax_ok' => true,
                'reloaded' => true,
            ],

            'modsec-test-request' => (function () use ($args): array {
                // Simüle: GET /admin?id=1' or 1=1-- → SQL injection match
                $uri = $args['uri'] ?? '/';
                $body = $args['body'] ?? '';
                $combined = strtolower($uri.' '.$body);

                $matchedRules = [];
                $score = 0;

                if (preg_match('/(\bor\b|\band\b)\s+\d+\s*=\s*\d+|union\s+select|drop\s+table/i', $combined)) {
                    $matchedRules[] = ['rule_id' => '942100', 'category' => 'SQL Injection', 'severity' => 'CRITICAL', 'score' => 5];
                    $score += 5;
                }
                if (preg_match('/<script|javascript:|onerror\s*=|onload\s*=/i', $combined)) {
                    $matchedRules[] = ['rule_id' => '941100', 'category' => 'XSS', 'severity' => 'CRITICAL', 'score' => 5];
                    $score += 5;
                }
                if (preg_match('/\.\.\/|\.\.\\\\/i', $combined)) {
                    $matchedRules[] = ['rule_id' => '930100', 'category' => 'Path Traversal', 'severity' => 'CRITICAL', 'score' => 5];
                    $score += 5;
                }
                if (preg_match('/\b(eval|exec|system|passthru)\s*\(/i', $combined)) {
                    $matchedRules[] = ['rule_id' => '933100', 'category' => 'PHP Injection', 'severity' => 'CRITICAL', 'score' => 5];
                    $score += 5;
                }

                return [
                    'matched_rules' => $matchedRules,
                    'anomaly_score' => $score,
                    'action' => $score >= 5 ? 'block' : 'allow',
                    'would_block' => $score >= 5,
                ];
            })(),

            'modsec-crs-update' => (function () use ($args): array {
                $checkOnly = (bool) ($args['check_only'] ?? false);
                $latestVersion = '4.7.0';
                $currentVersion = '3.3.5';

                if ($checkOnly) {
                    return [
                        'latest_version' => $latestVersion,
                        'installed_version' => $currentVersion,
                        'update_available' => true,
                        'release_url' => 'https://github.com/coreruleset/coreruleset/releases',
                    ];
                }

                return [
                    'from_version' => $currentVersion,
                    'installed_version' => $args['version'] ?? $latestVersion,
                    'rules_count' => 384,
                    'changelog' => 'CRS '.$latestVersion." released:\n- New SQL injection patterns\n- Improved bot detection\n- 47 false positive fixes",
                    'reloaded' => true,
                ];
            })(),

            'modsec-rule-detail' => (function () use ($args): array {
                $ruleId = $args['rule_id'] ?? '942100';
                $samples = [
                    '942100' => [
                        'source' => "SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS_NAMES|ARGS|XML:/* \"@detectSQLi\" \\\n    \"id:942100,\\\n    phase:2,\\\n    block,\\\n    capture,\\\n    t:none,t:utf8toUnicode,t:urlDecodeUni,t:removeNulls,\\\n    msg:'SQL Injection Attack Detected via libinjection',\\\n    logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\\\n    tag:'application-multi',\\\n    tag:'language-multi',\\\n    tag:'platform-multi',\\\n    tag:'attack-sqli',\\\n    tag:'OWASP_CRS',\\\n    tag:'capec/1000/152/248/66',\\\n    tag:'PCI/6.5.2',\\\n    severity:'CRITICAL',\\\n    setvar:'tx.sql_injection_score=+%{tx.critical_anomaly_score}',\\\n    setvar:'tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}'\"",
                        'category' => 'SQL Injection',
                        'severity' => 'CRITICAL',
                        'tag' => 'attack-sqli',
                        'description' => 'SQL Injection Attack Detected via libinjection',
                        'phase' => 2,
                        'file_path' => '/etc/httpd/modsecurity.d/owasp-crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf',
                    ],
                    '941100' => [
                        'source' => "SecRule REQUEST_COOKIES|...|XML:/* \"@detectXSS\" \\\n    \"id:941100,phase:2,block,capture,t:none,t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,...\"",
                        'category' => 'XSS',
                        'severity' => 'CRITICAL',
                        'tag' => 'attack-xss',
                        'description' => 'XSS Attack Detected via libinjection',
                        'phase' => 2,
                        'file_path' => '/etc/httpd/modsecurity.d/owasp-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf',
                    ],
                ];

                if (isset($samples[$ruleId])) {
                    return $samples[$ruleId];
                }

                // Generic fallback
                return [
                    'source' => "SecRule REQUEST_URI|ARGS \"@rx pattern_for_rule_$ruleId\" \\\n    \"id:$ruleId,phase:2,deny,status:403,msg:'Rule $ruleId triggered'\"",
                    'category' => 'Generic',
                    'severity' => ['CRITICAL', 'ERROR', 'WARNING'][rand(0, 2)],
                    'tag' => 'attack-generic',
                    'description' => "Rule $ruleId — pattern matching",
                    'phase' => 2,
                    'file_path' => '/etc/httpd/modsecurity.d/owasp-crs/rules/REQUEST-9XX.conf',
                ];
            })(),

            'modsec-rule-toggle' => [
                'rule_id' => $args['rule_id'] ?? '',
                'enabled' => $args['enabled'] ?? true,
                'file_written' => '/etc/httpd/modsecurity.d/onox-rule-states.conf',
                'reloaded' => true,
            ],

            'modsec-audit-event-detail' => (function () use ($args): array {
                $eventId = $args['event_id'] ?? '';

                return [
                    'event_id' => $eventId,
                    'request_headers' => "User-Agent: sqlmap/1.7.2\nAccept: */*\nHost: example.com\nX-Forwarded-For: 185.234.218.42\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 42",
                    'request_body' => "id=1' OR '1'='1&action=login&submit=Go",
                    'response_headers' => "HTTP/1.1 403 Forbidden\nServer: Apache\nContent-Type: text/html\nContent-Length: 218",
                    'all_matched_rules' => [
                        ['rule_id' => '942100', 'severity' => 'CRITICAL', 'msg' => 'SQL Injection via libinjection'],
                        ['rule_id' => '942130', 'severity' => 'CRITICAL', 'msg' => 'SQL injection attack: common SQL injection probings'],
                        ['rule_id' => '949110', 'severity' => 'CRITICAL', 'msg' => 'Inbound Anomaly Score Exceeded (Total Score: 10)'],
                    ],
                ];
            })(),

            'modsec-vendor-list' => [
                'vendors' => [
                    ['slug' => 'owasp_crs', 'version' => '3.3.5', 'rules_count' => 384, 'is_official' => true],
                ],
            ],

            'modsec-vendor-install' => (function () use ($args): array {
                return [
                    'slug' => $args['slug'] ?? '',
                    'version' => '1.0.0',
                    'rules_count' => rand(50, 300),
                    'reloaded' => true,
                ];
            })(),

            'modsec-vendor-remove' => [
                'slug' => $args['slug'] ?? '',
                'removed' => true,
                'reloaded' => true,
            ],

            'cert-revoke' => [
                'revoked' => true,
                'domain' => $args['domain'] ?? '',
                'acme_account' => 'production',
            ],

            'cgroup-read' => (function () use ($args): array {
                // Production'da gerçek cgroup.stat + memory.current okuma
                return [
                    'username' => $args['username'] ?? 'onx_demo',
                    'cpu_percent' => round(mt_rand(50, 8500) / 100, 2),
                    'memory_bytes' => rand(64_000_000, 512_000_000),
                    'memory_max_bytes' => 1_073_741_824,
                    'io_read_bytes' => rand(0, 50_000_000),
                    'io_write_bytes' => rand(0, 30_000_000),
                    'process_count' => rand(2, 25),
                    'tasks' => rand(5, 40),
                ];
            })(),

            // ── Firewall (firewalld | ufw | nftables) ────────────────────────
            'firewall-rule-add' => [
                'rule_id' => substr(hash('sha1', ($args['value'] ?? '').($args['type'] ?? '').microtime()), 0, 16),
                'applied' => true,
                'backend' => 'firewalld',
                'zone' => $args['zone'] ?? 'public',
                'duration_minutes' => (int) ($args['duration_minutes'] ?? 0),
                'scheduled_removal' => (int) ($args['duration_minutes'] ?? 0) > 0,
            ],
            'firewall-rule-remove' => [
                'removed' => true,
                'backend' => 'firewalld',
            ],
            'firewall-reload' => [
                'reloaded' => true,
                'backend' => 'firewalld',
            ],
            'firewall-list' => [
                'backend' => 'firewalld',
                'zones' => ['public', 'trusted', 'work', 'home'],
                'rules' => [
                    ['raw' => 'rule family=ipv4 source address="1.2.3.4" reject', 'type' => 'ip', 'value' => '1.2.3.4', 'action' => 'reject'],
                    ['raw' => 'rule family=ipv4 source address="5.6.7.0/24" drop', 'type' => 'ip', 'value' => '5.6.7.0/24', 'action' => 'drop'],
                    ['raw' => 'rule family=ipv4 port port="443" protocol="tcp" accept', 'type' => 'port', 'value' => '443', 'action' => 'accept'],
                    ['raw' => 'rule family=ipv4 service name="http" accept', 'type' => 'service', 'value' => 'http', 'action' => 'accept'],
                    ['raw' => 'rule family=ipv4 service name="ssh" accept', 'type' => 'service', 'value' => 'ssh', 'action' => 'accept'],
                ],
            ],
            'firewall-geoip-block' => (function () use ($args): array {
                $action = (string) ($args['action'] ?? 'block');
                $code = strtoupper((string) ($args['country_code'] ?? ''));
                if ($action === 'list') {
                    // Dev: pretend two countries are blocked
                    return ['blocked' => ['TR', 'RU'], 'count' => 2];
                }

                return [
                    'country' => $code,
                    'action' => $action,
                    'ipset' => 'onox-block-' . strtolower($code),
                    'backend' => 'nftables',
                    'applied' => true,
                    'ip_count' => $action === 'block' ? rand(800, 5_000) : 0,
                ];
            })(),
            'geoip-update' => (function () use ($args): array {
                $source = $args['source'] ?? 'auto';
                $effectiveSource = $source === 'auto' ? 'rir' : $source;
                $license = $effectiveSource === 'rir'
                    ? 'public domain (RIR delegated files)'
                    : 'CC BY 4.0 — DB-IP Lite';

                return [
                    'source' => $effectiveSource,
                    'updated_at' => now()->toIso8601String(),
                    'countries' => 249,  // ISO 3166-1 total country count
                    'total_cidrs' => rand(150_000, 250_000),  // ~190K RIR allocations
                    'cache_files' => 5,
                    'license' => $license,
                ];
            })(),

            // ── Firewall — Faz Ü gelişmiş özellikler ────────────────────────
            'firewall-threat-sync' => (function () use ($args): array {
                $slug = $args['slug'] ?? 'unknown';
                // Slug bazlı realistik IP count tahmini
                $counts = [
                    'spamhaus_drop'    => rand(800, 1200),
                    'spamhaus_edrop'   => rand(100, 200),
                    'dshield_top'      => 20,
                    'et_compromised'   => rand(15_000, 25_000),
                    'tor_exit'         => rand(900, 1500),
                    'firehol_level1'   => rand(5_000, 8_000),
                ];

                return [
                    'slug'      => $slug,
                    'ip_count'  => $counts[$slug] ?? rand(100, 5000),
                    'synced_at' => now()->toIso8601String(),
                    'set_name'  => "onox-threat-{$slug}",
                ];
            })(),
            'firewall-threat-toggle' => [
                'slug' => $args['slug'] ?? '',
                'action' => $args['action'] ?? 'enable',
                'applied' => true,
            ],
            'firewall-trusted-add' => [
                'ip' => $args['ip'] ?? '',
                'added_to_set' => 'onox-trusted',
                'priority' => 'highest',
            ],
            'firewall-trusted-remove' => [
                'ip' => $args['ip'] ?? '',
                'removed' => true,
            ],
            'firewall-conn-limits-apply' => [
                'applied' => true,
                'nftables_chain' => 'onox-ratelimit',
                'rules_count' => 8,
            ],
            'firewall-recent-blocks' => (function (): array {
                // Realistik blocked connection event örnekleri
                $samples = [];
                $services = ['ssh', 'http', 'https', 'smtp', 'ftp', 'panel'];
                $countries = ['CN', 'RU', 'KP', 'IR', 'BR', 'IN', 'VN', 'BY', 'PK', 'BD'];
                $ruleTypes = ['country', 'threat_list', 'cphulk', 'rule', 'rate_limit'];
                $now = now();

                for ($i = 0; $i < 20; $i++) {
                    $samples[] = [
                        'source_ip'    => long2ip(rand(1, 0xFFFFFFFF)),
                        'country_code' => $countries[array_rand($countries)],
                        'service'      => $services[array_rand($services)],
                        'rule_type'    => $ruleTypes[array_rand($ruleTypes)],
                        'action'       => 'blocked',
                        'hits'         => rand(1, 50),
                        'timestamp'    => $now->copy()->subMinutes(rand(0, 1440))->toDateTimeString(),
                    ];
                }

                return ['events' => $samples];
            })(),
            'firewall-ip-lookup' => (function () use ($args): array {
                $ip = $args['ip'] ?? '0.0.0.0';
                // Realistik whois/geoip mock
                $countries = ['US', 'GB', 'DE', 'NL', 'TR', 'RU', 'CN', 'BR', 'IN'];
                $orgs = ['Cloudflare Inc.', 'Amazon Technologies Inc.', 'Google LLC',
                         'Microsoft Corporation', 'OVH SAS', 'Hetzner Online GmbH',
                         'DigitalOcean LLC', 'Türk Telekom A.Ş.'];

                return [
                    'ip'           => $ip,
                    'reverse_dns'  => 'example-host-'.substr(md5($ip), 0, 8).'.example.net',
                    'country'      => $countries[array_rand($countries)],
                    'country_code' => $countries[array_rand($countries)],
                    'asn'          => 'AS'.rand(1000, 65000),
                    'org'          => $orgs[array_rand($orgs)],
                    'whois'        => 'Mock whois data for '.$ip,
                    'is_blocked'   => (bool) rand(0, 1),
                    'block_reason' => null,
                ];
            })(),

            'system-network-info' => [
                'interfaces' => [
                    [
                        'name' => 'eth0', 'state' => 'up',
                        'mac' => 'aa:bb:cc:dd:ee:01',
                        'ipv4' => '192.0.2.10',
                        'ipv6' => '2a01:4f8:c01:1234::1',
                        'netmask' => '255.255.255.0',
                        'gateway' => '192.0.2.1',
                        'mtu' => 1500,
                        'rx_bytes' => 2_847_392_011,
                        'tx_bytes' => 1_204_837_829,
                    ],
                    [
                        'name' => 'lo', 'state' => 'up',
                        'mac' => '00:00:00:00:00:00',
                        'ipv4' => '127.0.0.1',
                        'ipv6' => '::1',
                        'netmask' => '255.0.0.0',
                        'gateway' => null,
                        'mtu' => 65536,
                        'rx_bytes' => 1_204_837,
                        'tx_bytes' => 1_204_837,
                    ],
                ],
                'default_route' => '192.0.2.1',
                'dns_servers' => ['1.1.1.1', '8.8.8.8'],
            ],
            'system-listen-ports' => [
                'ports' => [
                    ['protocol' => 'tcp', 'port' => 22,   'address' => '0.0.0.0',   'service' => 'sshd',        'state' => 'LISTEN', 'pid' => 1011],
                    ['protocol' => 'tcp', 'port' => 25,   'address' => '0.0.0.0',   'service' => 'master',      'state' => 'LISTEN', 'pid' => 1233],
                    ['protocol' => 'tcp', 'port' => 80,   'address' => '0.0.0.0',   'service' => 'httpd',       'state' => 'LISTEN', 'pid' => 2154],
                    ['protocol' => 'tcp', 'port' => 443,  'address' => '0.0.0.0',   'service' => 'httpd',       'state' => 'LISTEN', 'pid' => 2154],
                    ['protocol' => 'tcp', 'port' => 993,  'address' => '0.0.0.0',   'service' => 'dovecot',     'state' => 'LISTEN', 'pid' => 3017],
                    ['protocol' => 'tcp', 'port' => 3306, 'address' => '127.0.0.1', 'service' => 'mariadbd',    'state' => 'LISTEN', 'pid' => 1881],
                    ['protocol' => 'udp', 'port' => 53,   'address' => '0.0.0.0',   'service' => 'pdns_server', 'state' => '-',      'pid' => 4012],
                ],
                'count' => 7,
            ],

            // ── fail2ban ─────────────────────────────────────────────────────
            'fail2ban-jail-write' => [
                'jail' => $args['jail_name'] ?? 'unknown',
                'applied' => true,
                'file' => '/etc/fail2ban/jail.d/'.($args['jail_name'] ?? 'unknown').'.local',
                'reloaded' => true,
            ],
            'fail2ban-ban' => [
                'jail' => $args['jail'] ?? 'sshd',
                'ip' => $args['ip'] ?? '0.0.0.0',
                'banned' => true,
            ],
            'fail2ban-unban' => [
                'jail' => $args['jail'] ?? 'sshd',
                'ip' => $args['ip'] ?? '0.0.0.0',
                'unbanned' => true,
            ],
            'fail2ban-status' => (function () use ($args): array {
                $jail = $args['jail'] ?? null;
                if (! $jail) {
                    return [
                        'running' => true,
                        'version' => '1.0.2',
                        'jails' => ['sshd', 'pure-ftpd', 'panel-login', 'recidive', 'postfix', 'dovecot', 'apache-badbots', 'wordpress-hard'],
                        'count' => 8,
                        'uptime_seconds' => rand(3600, 2_592_000),
                        'today_bans' => rand(15, 150),
                        'week_bans' => rand(200, 1500),
                        'total_bans_all_time' => rand(5000, 50_000),
                    ];
                }

                // Realistik IP'ler ve ülke kodları
                $ips = [];
                $countries = ['CN', 'RU', 'KP', 'IR', 'BR', 'IN', 'VN', 'BY', 'PK', 'BD', 'TR', 'US'];
                $banned_count = rand(0, 8);
                for ($i = 0; $i < $banned_count; $i++) {
                    $ips[] = long2ip(rand(1, 0xFFFFFFFF));
                }

                return [
                    'jail' => $jail,
                    'currently_failed' => rand(0, 5),
                    'currently_banned' => $banned_count,
                    'total_failed' => rand(20, 500),
                    'total_banned' => rand(5, 200),
                    'banned_ips' => $ips,
                    'file_list' => ['/var/log/secure'],
                ];
            })(),

            // ── fail2ban — Faz Ü gelişmiş: bulk ops, whitelist, config, history
            'fail2ban-jail-delete' => [
                'jail' => $args['jail_name'] ?? '',
                'deleted' => true,
                'file_removed' => '/etc/fail2ban/jail.d/'.($args['jail_name'] ?? '').'.local',
            ],
            'fail2ban-jail-toggle' => [
                'jail' => $args['jail_name'] ?? '',
                'enabled' => $args['enabled'] ?? true,
                'reloaded' => true,
            ],
            'fail2ban-bulk-ban' => (function () use ($args): array {
                $ips = $args['ips'] ?? [];
                return [
                    'jail' => $args['jail'] ?? 'sshd',
                    'requested' => count($ips),
                    'banned' => count($ips),
                    'failed' => 0,
                ];
            })(),
            'fail2ban-unban-all' => [
                'jail' => $args['jail'] ?? 'sshd',
                'unbanned_count' => rand(2, 20),
            ],
            'fail2ban-whitelist-write' => [
                'count' => count($args['ips'] ?? []),
                'applied' => true,
                'reloaded' => true,
            ],
            'fail2ban-settings-write' => [
                'updated_keys' => array_keys($args ?? []),
                'restarted' => true,
            ],
            'fail2ban-restart' => [
                'restarted' => true,
                'uptime_seconds' => 0,
            ],
            'fail2ban-reload' => [
                'reloaded' => true,
            ],
            'fail2ban-banned-list' => (function (): array {
                // Tüm jail'lerdeki aktif banlı IP'leri konsolide listele
                $jails = ['sshd', 'panel-login', 'postfix', 'dovecot', 'wordpress-hard', 'apache-badbots'];
                $countries = ['CN', 'RU', 'KP', 'IR', 'BR', 'IN', 'VN', 'BY', 'PK', 'BD'];
                $bans = [];

                for ($i = 0; $i < 25; $i++) {
                    $jail = $jails[array_rand($jails)];
                    $bans[] = [
                        'jail'         => $jail,
                        'ip'           => long2ip(rand(1, 0xFFFFFFFF)),
                        'country_code' => $countries[array_rand($countries)],
                        'banned_at'    => now()->subMinutes(rand(0, 4320))->toIso8601String(),
                        'expires_at'   => now()->addMinutes(rand(0, 1440))->toIso8601String(),
                        'failed_attempts' => rand(3, 50),
                    ];
                }

                return ['bans' => $bans, 'total' => count($bans)];
            })(),
            'fail2ban-history' => (function () use ($args): array {
                $limit = (int) ($args['limit'] ?? 50);
                $jails = ['sshd', 'panel-login', 'postfix', 'dovecot', 'wordpress-hard'];
                $actions = ['banned', 'unbanned', 'expired'];
                $countries = ['CN', 'RU', 'KP', 'IR', 'BR', 'TR', 'US'];
                $events = [];

                for ($i = 0; $i < $limit; $i++) {
                    $events[] = [
                        'jail'         => $jails[array_rand($jails)],
                        'ip'           => long2ip(rand(1, 0xFFFFFFFF)),
                        'country_code' => $countries[array_rand($countries)],
                        'action'       => $actions[array_rand($actions)],
                        'failed_attempts' => rand(3, 50),
                        'event_at'     => now()->subMinutes(rand(0, 10080))->toIso8601String(),
                    ];
                }

                return ['events' => $events];
            })(),
            'fail2ban-stats' => (function (): array {
                // 24 saatlik ban grafiği için saatlik veri
                $hourly = [];
                for ($h = 23; $h >= 0; $h--) {
                    $hourly[] = [
                        'hour' => now()->subHours($h)->format('H:00'),
                        'bans' => rand(0, 30),
                        'failed' => rand(5, 100),
                    ];
                }

                return [
                    'today_bans'        => rand(20, 150),
                    'today_failed'      => rand(500, 3000),
                    'week_bans'         => rand(300, 1500),
                    'month_bans'        => rand(1500, 8000),
                    'currently_banned'  => rand(15, 80),
                    'top_attackers_24h' => array_map(fn() => [
                        'ip' => long2ip(rand(1, 0xFFFFFFFF)),
                        'country_code' => ['CN', 'RU', 'KP', 'IR', 'BR'][rand(0, 4)],
                        'attempts' => rand(20, 200),
                        'banned'   => (bool) rand(0, 1),
                    ], range(0, 9)),
                    'bans_by_jail' => [
                        ['jail' => 'sshd',           'count' => rand(30, 200)],
                        ['jail' => 'panel-login',    'count' => rand(20, 100)],
                        ['jail' => 'wordpress-hard', 'count' => rand(15, 80)],
                        ['jail' => 'postfix',        'count' => rand(10, 60)],
                        ['jail' => 'dovecot',        'count' => rand(5, 40)],
                        ['jail' => 'apache-badbots', 'count' => rand(20, 90)],
                    ],
                    'hourly' => $hourly,
                ];
            })(),
            'fail2ban-filter-test' => [
                'matches' => rand(0, 5),
                'matched_lines' => $args['matches_demo'] ?? [
                    'Failed password for root from 1.2.3.4 port 22',
                    'Invalid user admin from 5.6.7.8 port 22',
                ],
                'failregex_ok' => true,
            ],
            'fail2ban-filter-list' => [
                'filters' => [
                    'sshd', 'sshd-ddos', 'apache-auth', 'apache-badbots', 'apache-nohome',
                    'apache-overflows', 'apache-shellshock', 'nginx-http-auth', 'nginx-botsearch',
                    'postfix', 'postfix-sasl', 'dovecot', 'pure-ftpd', 'proftpd', 'vsftpd',
                    'mysqld-auth', 'wordpress-hard', 'wordpress-soft', 'phpmyadmin-syslog',
                    'recidive', 'panel-login', 'panel-api', 'named-refused',
                ],
            ],

            // ── Postfix per-domain transport (cPanel Email Routing muadili) ──
            'postfix-transport-write' => (function () use ($args): array {
                $domain = $args['domain'] ?? 'example.com';
                $mode = $args['mode'] ?? 'auto';
                $remoteHost = $args['remote_host'] ?? null;
                $remotePort = (int) ($args['remote_port'] ?? 25);

                $entry = match ($mode) {
                    'local'  => "{$domain}\tlocal:",
                    'remote' => $remoteHost !== null
                        ? "{$domain}\tsmtp:[{$remoteHost}]:{$remotePort}"
                        : "{$domain}\tsmtp:",
                    'backup' => $remoteHost !== null
                        ? "{$domain}\tsmtp:[{$remoteHost}]:{$remotePort}"
                        : "{$domain}\trelay:",
                    default  => "{$domain}\t:",
                };

                return [
                    'transport_path' => '/etc/postfix/transport',
                    'entries_written' => 1,
                    'reload_required' => true,
                    'mode' => $mode,
                    'domain' => $domain,
                    'entry' => $entry,
                ];
            })(),

            // ── Webmail / Mail stack install (mock) ──────────────────────────
            'mailserver-install' => ['installed' => true, 'driver' => $args['driver'] ?? 'postfix-dovecot'],
            'mail-stack-install' => ['installed' => true, 'components' => ['postfix', 'dovecot', 'rspamd', 'roundcube']],
            'mail-stack-status' => ['all_running' => true, 'mta' => 'running', 'imap' => 'running', 'spam' => 'running'],
            'webmail-install' => ['installed' => true, 'driver' => $args['driver'] ?? 'roundcube'],
            'webmail-status' => ['installed' => true, 'driver' => 'roundcube', 'url' => '/webmail'],
            // ── Disk usage analyzer (per-account breakdown) ──────────────────
            'du-account' => (function () use ($args): array {
                $username = $args['username'] ?? 'onx_demo';
                $home = $args['home'] ?? "/home/{$username}";
                $topCount = (int) ($args['top_count'] ?? 20);

                $publicHtml = rand(400_000_000, 1_200_000_000);
                $maildir = rand(50_000_000, 400_000_000);
                $databases = rand(20_000_000, 250_000_000);
                $logs = rand(5_000_000, 80_000_000);
                $backups = rand(0, 200_000_000);
                $tmp = rand(0, 30_000_000);
                $private = rand(0, 10_000_000);
                $total = $publicHtml + $maildir + $databases + $logs + $backups + $tmp + $private;

                $topFiles = [];
                $sampleFiles = [
                    ['public_html/wp-content/uploads/2026/05/video-large.mp4', 'video/mp4'],
                    ['public_html/wp-content/uploads/2024/backup.zip', 'application/zip'],
                    ['public_html/wp-content/cache/all/.cache-large.html', 'text/html'],
                    ['logs/access.log', 'text/plain'],
                    ['public_html/wp-content/uploads/2025/photo-set.tar', 'application/x-tar'],
                    ['backups/full-' . now()->format('Ymd') . '.tar.gz', 'application/gzip'],
                ];
                foreach (array_slice($sampleFiles, 0, min($topCount, count($sampleFiles))) as $i => $sf) {
                    $topFiles[] = [
                        'path' => $sf[0],
                        'size' => rand(50_000_000, 250_000_000) - ($i * 10_000_000),
                        'mime' => $sf[1],
                    ];
                }

                $topDirs = [
                    ['path' => 'public_html/wp-content/uploads', 'size' => 720_000_000, 'file_count' => 4521],
                    ['path' => 'public_html/wp-content/cache', 'size' => 320_000_000, 'file_count' => 8924],
                    ['path' => 'Maildir/cur', 'size' => 280_000_000, 'file_count' => 12_345],
                    ['path' => 'logs', 'size' => 145_000_000, 'file_count' => 24],
                    ['path' => 'public_html/wp-content/plugins', 'size' => 95_000_000, 'file_count' => 1856],
                ];

                return [
                    'username' => $username,
                    'home' => $home,
                    'total_bytes' => $total,
                    'breakdown' => [
                        'public_html' => $publicHtml,
                        'Maildir' => $maildir,
                        'databases' => $databases,
                        'logs' => $logs,
                        'backups' => $backups,
                        'tmp' => $tmp,
                        'private' => $private,
                    ],
                    'top_files' => $topFiles,
                    'top_directories' => array_slice($topDirs, 0, $topCount),
                ];
            })(),

            // ── Stats — access log parse (visitors) ─────────────────────────
            'access-log-parse' => (function () use ($args): array {
                $domain = $args['domain'] ?? 'example.com';
                $hourly = [];
                for ($h = 0; $h < 24; $h++) {
                    $hourly[] = ['hour' => sprintf('%02d:00', $h), 'visits' => rand(15, 220)];
                }

                return [
                    'domain' => $domain,
                    'log_file' => "/home/" . ($args['username'] ?? 'unknown') . "/logs/{$domain}-access.log",
                    'lines_scanned' => rand(8000, 95000),
                    'total_visits' => array_sum(array_column($hourly, 'visits')),
                    'unique_visitors' => rand(450, 2400),
                    'hourly' => $hourly,
                    'top_pages' => [
                        ['path' => '/', 'hits' => rand(800, 4500)],
                        ['path' => '/iletisim', 'hits' => rand(120, 600)],
                        ['path' => '/hakkimizda', 'hits' => rand(80, 400)],
                        ['path' => '/blog', 'hits' => rand(60, 350)],
                        ['path' => '/urunler', 'hits' => rand(40, 220)],
                    ],
                    'top_referrers' => [
                        ['referrer' => 'google.com', 'hits' => rand(200, 1500)],
                        ['referrer' => 'facebook.com', 'hits' => rand(40, 200)],
                        ['referrer' => 'instagram.com', 'hits' => rand(20, 120)],
                        ['referrer' => 'twitter.com', 'hits' => rand(10, 80)],
                        ['referrer' => 'duckduckgo.com', 'hits' => rand(5, 40)],
                    ],
                    'top_user_agents' => [
                        ['agent' => 'Chrome', 'hits' => rand(2500, 6500)],
                        ['agent' => 'Safari', 'hits' => rand(500, 1700)],
                        ['agent' => 'Firefox', 'hits' => rand(300, 900)],
                        ['agent' => 'Edge', 'hits' => rand(200, 700)],
                        ['agent' => 'Bot', 'hits' => rand(100, 400)],
                    ],
                    'top_countries' => [],
                    'engine' => 'awk',
                ];
            })(),

            // ── Stats — error log parse ─────────────────────────────────────
            'error-log-parse' => (function () use ($args): array {
                $domain = $args['domain'] ?? 'example.com';
                $grouped = [
                    [
                        'signature' => 'File does not exist: /var/www/html/favicon.ico',
                        'severity' => 'warn',
                        'count' => rand(20, 200),
                        'first_seen' => now()->subHours(20)->toDateTimeString(),
                        'last_seen' => now()->subMinutes(5)->toDateTimeString(),
                        'sample_ip' => '203.0.113.42',
                    ],
                    [
                        'signature' => 'PHP Warning: include(missing.php): Failed to open stream',
                        'severity' => 'warn',
                        'count' => rand(5, 45),
                        'first_seen' => now()->subHours(8)->toDateTimeString(),
                        'last_seen' => now()->subMinutes(35)->toDateTimeString(),
                        'sample_ip' => '198.51.100.7',
                    ],
                    [
                        'signature' => 'AH01797: client denied by server configuration: /var/www/html/.git/',
                        'severity' => 'error',
                        'count' => rand(2, 15),
                        'first_seen' => now()->subHours(14)->toDateTimeString(),
                        'last_seen' => now()->subHours(2)->toDateTimeString(),
                        'sample_ip' => '185.234.218.42',
                    ],
                    [
                        'signature' => 'PHP Fatal error: Allowed memory size of N bytes exhausted',
                        'severity' => 'critical',
                        'count' => rand(0, 5),
                        'first_seen' => now()->subDay()->toDateTimeString(),
                        'last_seen' => now()->subHours(6)->toDateTimeString(),
                        'sample_ip' => null,
                    ],
                ];

                $recent = [];
                $samples = [
                    'File does not exist: /var/www/html/favicon.ico',
                    'AH01797: client denied by server configuration: /var/www/html/.git/',
                    'PHP Warning: include(missing.php): Failed to open stream',
                    'mod_security: Access denied with code 403 (phase 2)',
                ];
                $levels = ['warn', 'error', 'notice', 'critical'];
                for ($i = 0; $i < 5; $i++) {
                    $recent[] = [
                        'timestamp' => now()->subMinutes(rand(0, 300))->toDateTimeString(),
                        'level' => $levels[array_rand($levels)],
                        'client' => sprintf('%d.%d.%d.%d', rand(1, 254), rand(0, 255), rand(0, 255), rand(0, 255)),
                        'message' => $samples[array_rand($samples)],
                    ];
                }

                return [
                    'domain' => $domain,
                    'log_file' => "/home/" . ($args['username'] ?? 'unknown') . "/logs/{$domain}-error.log",
                    'lines_scanned' => (int) ($args['lines'] ?? 500),
                    'by_severity' => [
                        'critical' => $grouped[3]['count'],
                        'error' => $grouped[2]['count'],
                        'warn' => $grouped[0]['count'] + $grouped[1]['count'],
                        'notice' => rand(10, 80),
                    ],
                    'grouped' => $grouped,
                    'recent' => $recent,
                ];
            })(),

            // ── GPG — per-user keyring (mock) ────────────────────────────────
            'gpg-key-gen' => (function () use ($args): array {
                $fp = strtoupper(bin2hex(random_bytes(20))); // 40-char hex
                $keyId = substr($fp, -16);
                $body = base64_encode(random_bytes(192));
                $pub = implode("\n", [
                    '-----BEGIN PGP PUBLIC KEY BLOCK-----',
                    '',
                    substr($body, 0, 64),
                    substr($body, 64, 64),
                    substr($body, 128, 64),
                    substr($body, 192, 12).'==',
                    '='.strtoupper(substr(bin2hex(random_bytes(2)), 0, 4)),
                    '-----END PGP PUBLIC KEY BLOCK-----',
                ]);

                return [
                    'fingerprint' => $fp,
                    'key_id' => $keyId,
                    'public_key' => $pub,
                    'created' => true,
                ];
            })(),
            'gpg-key-import' => (function () use ($args): array {
                $body = (string) ($args['public_key'] ?? '');
                preg_match('/[A-F0-9]{40}/i', $body, $m);
                $fp = isset($m[0]) ? strtoupper($m[0]) : strtoupper(bin2hex(random_bytes(20)));

                return [
                    'fingerprint' => $fp,
                    'key_id' => substr($fp, -16),
                    'imported' => true,
                ];
            })(),
            'gpg-key-list' => (function () use ($args): array {
                $username = $args['username'] ?? 'unknown';
                $keys = [];
                $count = rand(0, 2);
                for ($i = 0; $i < $count; $i++) {
                    $fp = strtoupper(bin2hex(random_bytes(20)));
                    $keys[] = [
                        'fingerprint' => $fp,
                        'key_id' => substr($fp, -16),
                        'uid' => ($i === 0 ? 'Demo User <demo@example.com>' : 'Backup <backup@example.com>'),
                        'key_size' => $i === 0 ? 4096 : 2048,
                        'created_at' => now()->subMonths($i)->format('Y-m-d'),
                        'expires_at' => null,
                        'has_private' => true,
                    ];
                }

                return ['username' => $username, 'count' => $count, 'keys' => $keys];
            })(),
            'gpg-key-delete' => [
                'fingerprint' => $args['fingerprint'] ?? '',
                'deleted' => true,
            ],
            'gpg-key-export' => (function () use ($args): array {
                $fp = (string) ($args['fingerprint'] ?? strtoupper(bin2hex(random_bytes(20))));
                $body = base64_encode(random_bytes(192));
                $kind = $args['kind'] ?? 'public';
                $blockType = $kind === 'secret' ? 'PRIVATE KEY BLOCK' : 'PUBLIC KEY BLOCK';
                $armor = implode("\n", [
                    "-----BEGIN PGP {$blockType}-----",
                    '',
                    substr($body, 0, 64),
                    substr($body, 64, 64),
                    substr($body, 128, 64),
                    substr($body, 192, 12).'==',
                    '='.strtoupper(substr(bin2hex(random_bytes(2)), 0, 4)),
                    "-----END PGP {$blockType}-----",
                ]);

                return [
                    'fingerprint' => $fp,
                    'armor' => $armor,
                    'kind' => $kind,
                ];
            })(),

            // ── Git — clone / pull / deploy / status (mock) ──────────────────
            'git-clone' => (function () use ($args): array {
                $sha = substr(sha1(($args['path'] ?? '') . microtime()), 0, 40);

                return [
                    'path' => $args['path'] ?? '/home/unknown/repos/demo',
                    'branch' => $args['branch'] ?? 'main',
                    'last_commit_sha' => $sha,
                    'last_commit_message' => 'Initial clone (mock)',
                    'last_commit_author' => 'demo@example.com',
                    'last_commit_date' => now()->toIso8601String(),
                    'cloned' => true,
                    'pulled' => false,
                ];
            })(),
            'git-pull' => (function () use ($args): array {
                $sha = substr(sha1(($args['path'] ?? '') . microtime()), 0, 40);

                return [
                    'path' => $args['path'] ?? '/home/unknown/repos/demo',
                    'branch' => $args['branch'] ?? 'main',
                    'last_commit_sha' => $sha,
                    'last_commit_message' => 'Merge pull request #'.rand(10, 99),
                    'last_commit_author' => 'demo@example.com',
                    'last_commit_date' => now()->toIso8601String(),
                    'files_changed' => rand(0, 12),
                    'ahead' => 0,
                    'behind' => 0,
                ];
            })(),
            'git-deploy' => (function () use ($args): array {
                $sha = substr(sha1(($args['path'] ?? '') . microtime()), 0, 40);

                return [
                    'deployed' => true,
                    'last_commit_sha' => $sha,
                    'last_commit_message' => 'Deploy: bump version',
                    'files_changed' => rand(1, 8),
                    'script_output' => "composer install: ok\nphp artisan migrate: ok",
                    'duration_seconds' => rand(8, 45),
                ];
            })(),
            'git-status' => (function () use ($args): array {
                $sha = substr(sha1(($args['path'] ?? '') . microtime()), 0, 40);

                return [
                    'path' => $args['path'] ?? '/home/unknown/repos/demo',
                    'branch' => 'main',
                    'last_commit_sha' => $sha,
                    'last_commit_message' => 'Latest commit (mock)',
                    'last_commit_author' => 'demo@example.com',
                    'last_commit_date' => now()->toIso8601String(),
                    'ahead' => 0,
                    'behind' => 0,
                    'dirty_files' => 0,
                    'untracked_files' => 0,
                    'status' => 'clean',
                ];
            })(),

            // ── Replication / HA (Phase 3) ────────────────────────────────────
            'mariadb-master-setup' => [
                'server_id' => (int) ($args['server_id'] ?? 1),
                'repl_user' => $args['repl_user'] ?? 'replicator',
                'log_file' => 'mysql-bin.'.str_pad((string) rand(1, 999), 6, '0', STR_PAD_LEFT),
                'log_pos' => rand(100, 999_999),
                'binlog_format' => 'ROW',
                'gtid_state' => '0-1-'.rand(100, 9999),
            ],
            'mariadb-slave-setup' => [
                'server_id' => (int) ($args['server_id'] ?? 2),
                'master_host' => $args['master_host'] ?? 'unknown',
                'master_port' => (int) ($args['master_port'] ?? 3306),
                'log_file' => $args['log_file'] ?? 'mysql-bin.000001',
                'log_pos' => (int) ($args['log_pos'] ?? 0),
                'slave_io_running' => 'Yes',
                'slave_sql_running' => 'Yes',
                'configured' => true,
            ],
            'mariadb-slave-status' => [
                'slave_io_running' => 'Yes',
                'slave_sql_running' => 'Yes',
                'seconds_behind_master' => rand(0, 5),
                'master_host' => $args['master_host'] ?? '10.0.0.1',
                'master_log_file' => 'mysql-bin.'.str_pad((string) rand(1, 999), 6, '0', STR_PAD_LEFT),
                'read_master_log_pos' => rand(1000, 999_999),
                'exec_master_log_pos' => rand(1000, 999_999),
                'last_io_error' => '',
                'last_sql_error' => '',
                'last_errno' => 0,
            ],
            'mariadb-promote' => [
                'promoted' => true,
                'previous_role' => 'slave',
                'new_role' => 'master',
                'read_only_disabled' => true,
                'reset_master_log_file' => 'mysql-bin.000001',
                'reset_master_log_pos' => 4,
            ],
            'mariadb-rebuild' => [
                'rebuilt' => true,
                'master_host' => $args['master_host'] ?? 'unknown',
                'method' => 'mariabackup',
                'bytes_transferred' => rand(500_000_000, 5_000_000_000),
                'duration_seconds' => rand(180, 3600),
                'slave_io_running' => 'Yes',
                'slave_sql_running' => 'Yes',
            ],
            'pdns-setup-slave' => [
                'configured' => true,
                'master_ip' => $args['master_ip'] ?? '10.0.0.1',
                'slave_host' => $args['slave_host'] ?? 'unknown',
                'backend' => 'gmysql',
                'zone_count' => rand(5, 200),
                'last_axfr' => now()->subSeconds(rand(0, 120))->toIso8601String(),
                'allow_axfr_from' => $args['master_ip'] ?? '10.0.0.1',
            ],
            'pdns-promote' => (function () use ($args): array {
                $action = $args['action'] ?? 'promote';
                if ($action === 'notify') {
                    return [
                        'zone' => $args['zone'] ?? 'example.com',
                        'notified' => true,
                        'recipients' => rand(1, 4),
                    ];
                }
                if ($action === 'list-zones') {
                    return [
                        'slave_host' => $args['slave_host'] ?? 'unknown',
                        'zones' => array_map(fn ($i) => [
                            'name' => "zone-{$i}.example.com",
                            'serial' => time() - rand(0, 86400),
                            'type' => 'SLAVE',
                        ], range(1, rand(3, 8))),
                    ];
                }

                return [
                    'promoted' => true,
                    'previous_type' => 'SLAVE',
                    'new_type' => 'MASTER',
                    'zones_converted' => rand(5, 50),
                ];
            })(),
            'redis-sentinel-setup' => [
                'master' => $args['master_host'] ?? 'redis-master',
                'master_port' => (int) ($args['master_port'] ?? 6379),
                'quorum' => (int) ($args['quorum'] ?? 2),
                'sentinels' => array_map(fn ($i) => [
                    'host' => "sentinel-{$i}.cluster.local",
                    'port' => 26379,
                    'role' => 'sentinel',
                    'reachable' => true,
                ], range(1, max(3, (int) ($args['quorum'] ?? 2) + 1))),
                'down_after_milliseconds' => 5000,
                'failover_timeout' => 60000,
                'configured' => true,
            ],
            'redis-replicaof' => [
                'master_host' => $args['master_host'] ?? 'redis-master',
                'master_port' => (int) ($args['master_port'] ?? 6379),
                'role' => 'replica',
                'master_link_status' => 'up',
                'master_last_io_seconds_ago' => 0,
                'connected_slaves' => 0,
            ],

            // ── Cluster (Phase N enterprise) ─────────────────────────────────
            'cluster-sync-state' => (function () use ($args): array {
                $scope = (array) ($args['scope'] ?? ['users', 'accounts', 'packages', 'domains']);

                return [
                    'synced' => true,
                    'master_url' => $args['master_url'] ?? config('app.url'),
                    'scope' => $scope,
                    'rows_received' => array_combine($scope, array_map(fn () => rand(20, 4500), $scope)),
                    'bytes_transferred' => rand(150_000, 8_000_000),
                    'duration_ms' => rand(150, 4500),
                    'applied_at' => now()->toIso8601String(),
                ];
            })(),
            'account-migrate' => (function () use ($args): array {
                $accountId = (int) ($args['account_id'] ?? 0);
                $username = $args['username'] ?? 'onx_acme01';
                $targetNode = $args['target_node'] ?? 'web2.cluster.local';
                $snapshotId = $args['snapshot_id'] ?? \Illuminate\Support\Str::uuid()->toString();

                return [
                    'account_id' => $accountId,
                    'username' => $username,
                    'source_node' => $args['source_node'] ?? 'web1.cluster.local',
                    'target_node' => $targetNode,
                    'snapshot_id' => $snapshotId,
                    'phases' => [
                        ['name' => 'pre-flight', 'status' => 'completed', 'duration_s' => rand(2, 6)],
                        ['name' => 'snapshot-create', 'status' => 'completed', 'duration_s' => rand(45, 240)],
                        ['name' => 'rsync-transfer', 'status' => 'completed', 'duration_s' => rand(60, 480)],
                        ['name' => 'snapshot-restore', 'status' => 'completed', 'duration_s' => rand(60, 300)],
                        ['name' => 'dns-update', 'status' => 'completed', 'duration_s' => rand(2, 8)],
                        ['name' => 'source-readonly', 'status' => 'completed', 'duration_s' => 1],
                    ],
                    'status' => 'migrated',
                    'eta_seconds' => 0,
                    'bytes_transferred' => rand(120_000_000, 4_500_000_000),
                    'downtime_seconds' => rand(2, 30),
                    'completed_at' => now()->toIso8601String(),
                ];
            })(),
            'snapshot-receive' => (function () use ($args): array {
                $snapshotId = $args['snapshot_id'] ?? \Illuminate\Support\Str::uuid()->toString();
                $sourcePath = $args['source_path'] ?? '/var/lib/onox/snapshots/source.tar.gz';
                $targetPath = $args['target_path'] ?? "/var/lib/onox/snapshots/{$snapshotId}.tar.gz";

                return [
                    'snapshot_id' => $snapshotId,
                    'source_path' => $sourcePath,
                    'target_path' => $targetPath,
                    'bytes_received' => rand(100_000_000, 5_000_000_000),
                    'sha256_match' => true,
                    'transport' => 'rsync+ssh',
                    'duration_s' => rand(60, 600),
                    'received_at' => now()->toIso8601String(),
                ];
            })(),
            'cluster-token-rotate' => (function () use ($args): array {
                return [
                    'node_id' => (int) ($args['node_id'] ?? 0),
                    'rotated' => true,
                    'previous_token_revoked_at' => now()->toIso8601String(),
                    'grace_period_seconds' => 60,
                    'next_heartbeat_will_adapt' => true,
                ];
            })(),

            // ── Process Manager (ps aux + kill) ───────────────────────────────
            'process-list' => (function () use ($args): array {
                $limit = (int) ($args['limit'] ?? 50);
                $sort = (string) ($args['sort'] ?? 'cpu');
                $filterUser = $args['filter_user'] ?? null;

                // Realistic mix: ~30 processes (httpd workers, mariadb, php-fpm
                // pools, redis, postfix, nginx, sshd, cron, systemd, horizon).
                $samples = [
                    ['user' => 'root',       'cmd' => '/usr/lib/systemd/systemd --system --deserialize 18', 'cpu' => 0.0,  'mem' => 0.5,  'state' => 'S'],
                    ['user' => 'root',       'cmd' => '/usr/lib/systemd/systemd-journald',                  'cpu' => 0.1,  'mem' => 0.4,  'state' => 'S'],
                    ['user' => 'root',       'cmd' => '/usr/sbin/sshd -D',                                   'cpu' => 0.0,  'mem' => 0.2,  'state' => 'S'],
                    ['user' => 'root',       'cmd' => '/usr/sbin/crond -n',                                  'cpu' => 0.0,  'mem' => 0.1,  'state' => 'S'],
                    ['user' => 'root',       'cmd' => '/usr/sbin/chronyd -F 2',                              'cpu' => 0.0,  'mem' => 0.1,  'state' => 'S'],
                    ['user' => 'mysql',      'cmd' => '/usr/libexec/mariadbd --basedir=/usr',                'cpu' => 12.4, 'mem' => 14.5, 'state' => 'S'],
                    ['user' => 'mysql',      'cmd' => '/usr/libexec/mariadbd --basedir=/usr',                'cpu' => 2.3,  'mem' => 14.5, 'state' => 'S'],
                    ['user' => 'apache',     'cmd' => '/usr/sbin/httpd -DFOREGROUND',                        'cpu' => 1.8,  'mem' => 3.2,  'state' => 'S'],
                    ['user' => 'apache',     'cmd' => '/usr/sbin/httpd -DFOREGROUND',                        'cpu' => 0.8,  'mem' => 2.1,  'state' => 'S'],
                    ['user' => 'apache',     'cmd' => '/usr/sbin/httpd -DFOREGROUND',                        'cpu' => 1.2,  'mem' => 2.8,  'state' => 'R'],
                    ['user' => 'apache',     'cmd' => '/usr/sbin/httpd -DFOREGROUND',                        'cpu' => 4.6,  'mem' => 3.5,  'state' => 'R'],
                    ['user' => 'nginx',      'cmd' => 'nginx: worker process',                                'cpu' => 0.4,  'mem' => 0.6,  'state' => 'S'],
                    ['user' => 'nginx',      'cmd' => 'nginx: worker process',                                'cpu' => 0.3,  'mem' => 0.6,  'state' => 'S'],
                    ['user' => 'onx_acme01', 'cmd' => 'php-fpm: pool onx_acme01_php82',                       'cpu' => 3.5,  'mem' => 4.2,  'state' => 'R'],
                    ['user' => 'onx_acme01', 'cmd' => 'php-fpm: pool onx_acme01_php82',                       'cpu' => 2.1,  'mem' => 3.8,  'state' => 'S'],
                    ['user' => 'onx_blog42', 'cmd' => 'php-fpm: pool onx_blog42_php82',                       'cpu' => 1.8,  'mem' => 3.1,  'state' => 'S'],
                    ['user' => 'onx_blog42', 'cmd' => 'php-fpm: pool onx_blog42_php82',                       'cpu' => 5.7,  'mem' => 4.4,  'state' => 'R'],
                    ['user' => 'onx_shop99', 'cmd' => 'php-fpm: pool onx_shop99_php83',                       'cpu' => 87.3, 'mem' => 18.6, 'state' => 'R'],
                    ['user' => 'onx_shop99', 'cmd' => 'php-fpm: pool onx_shop99_php83',                       'cpu' => 8.7,  'mem' => 6.5,  'state' => 'R'],
                    ['user' => 'onx_shop99', 'cmd' => 'php-fpm: pool onx_shop99_php83',                       'cpu' => 4.2,  'mem' => 4.1,  'state' => 'S'],
                    ['user' => 'onx_kafe',   'cmd' => 'php-fpm: pool onx_kafe_php82',                          'cpu' => 0.6,  'mem' => 1.4,  'state' => 'S'],
                    ['user' => 'pdns',       'cmd' => '/usr/sbin/pdns_server --daemon=no --guardian=no',      'cpu' => 0.1,  'mem' => 0.8,  'state' => 'S'],
                    ['user' => 'redis',      'cmd' => '/usr/bin/redis-server *:6379',                         'cpu' => 0.3,  'mem' => 1.2,  'state' => 'S'],
                    ['user' => 'postfix',    'cmd' => 'qmgr -l -t unix -u',                                    'cpu' => 0.0,  'mem' => 0.3,  'state' => 'S'],
                    ['user' => 'postfix',    'cmd' => 'pickup -l -t unix -u',                                  'cpu' => 0.0,  'mem' => 0.3,  'state' => 'S'],
                    ['user' => 'dovecot',    'cmd' => '/usr/sbin/dovecot -F',                                  'cpu' => 0.2,  'mem' => 0.7,  'state' => 'S'],
                    ['user' => 'root',       'cmd' => '/usr/bin/python3 /usr/bin/fail2ban-server -f',         'cpu' => 0.2,  'mem' => 1.5,  'state' => 'S'],
                    ['user' => 'apache',     'cmd' => 'horizon:supervisor:default',                            'cpu' => 1.5,  'mem' => 2.4,  'state' => 'S'],
                    ['user' => 'apache',     'cmd' => 'horizon:worker provisioning',                           'cpu' => 67.4, 'mem' => 5.8,  'state' => 'R'],
                    ['user' => 'apache',     'cmd' => 'horizon:worker default',                                'cpu' => 0.8,  'mem' => 1.8,  'state' => 'S'],
                    ['user' => 'root',       'cmd' => '/usr/bin/clamd -F',                                     'cpu' => 0.1,  'mem' => 9.4,  'state' => 'S'],
                    ['user' => 'root',       'cmd' => 'rspamd: main process',                                  'cpu' => 0.3,  'mem' => 2.2,  'state' => 'S'],
                ];

                $rows = [];
                foreach ($samples as $i => $p) {
                    $vsz = rand(50_000, 1_500_000);
                    $rss = (int) ($vsz * (0.2 + (mt_rand(0, 30) / 100)));
                    $memMb = round($rss / 1024, 1);
                    $runtimeMin = rand(1, 60 * 48);
                    $rows[] = [
                        // Canonical fields per spec
                        'pid' => 1000 + ($i * 137) + rand(0, 50),
                        'user' => $p['user'],
                        'cpu_pct' => (float) $p['cpu'],
                        'mem_pct' => (float) $p['mem'],
                        'mem_mb' => $memMb,
                        'vsz_kb' => $vsz,
                        'rss_kb' => $rss,
                        'tty' => '?',
                        'stat' => $p['state'],
                        'start_time' => now()->subMinutes($runtimeMin)->format('M d H:i'),
                        'runtime' => sprintf('%02d:%02d:%02d', intdiv($runtimeMin, 60), $runtimeMin % 60, rand(0, 59)),
                        'runtime_seconds' => $runtimeMin * 60,
                        'command' => explode(' ', $p['cmd'], 2)[0],
                        'args' => $p['cmd'],
                        // Backward-compatible aliases (legacy keys still consumed elsewhere).
                        'cpu' => (float) $p['cpu'],
                        'mem' => (float) $p['mem'],
                        'cmd' => $p['cmd'],
                        'state' => $p['state'],
                        'time' => sprintf('%d:%02d', intdiv($runtimeMin, 60), $runtimeMin % 60),
                        'started' => now()->subMinutes($runtimeMin)->format('M d H:i'),
                    ];
                }

                $sortKey = match ($sort) {
                    'mem' => 'mem_pct',
                    'pid' => 'pid',
                    'runtime', 'time' => 'runtime_seconds',
                    default => 'cpu_pct',
                };

                $processes = collect($rows)
                    ->when($filterUser, fn ($c) => $c->filter(fn ($p) => $p['user'] === $filterUser))
                    ->sortByDesc(fn ($p) => $p[$sortKey] ?? 0)
                    ->take($limit)
                    ->values()
                    ->all();

                $running = collect($processes)->where('stat', 'R')->count();
                $sleeping = collect($processes)->where('stat', 'S')->count();

                return [
                    'processes' => $processes,
                    'summary' => [
                        'total' => 156,
                        'running' => max(1, $running),
                        'sleeping' => max(0, $sleeping + 130),
                        'zombie' => 0,
                        'stopped' => rand(0, 3),
                    ],
                    'load' => [
                        '1m' => round(rand(40, 220) / 100, 2),
                        '5m' => round(rand(40, 200) / 100, 2),
                        '15m' => round(rand(40, 180) / 100, 2),
                        'cores' => 4,
                    ],
                ];
            })(),
            'process-kill' => (function () use ($args): array {
                $sigInt = (int) ($args['signal'] ?? 15);
                return [
                    'pid' => (int) ($args['pid'] ?? 0),
                    'killed' => true,
                    'signal' => $sigInt === 9 ? 'KILL' : ($sigInt === 1 ? 'HUP' : 'TERM'),
                ];
            })(),

            // ── Reseller white-label vhost (Phase 3) ─────────────────────────
            'reseller-vhost-add' => (function () use ($args): array {
                $resellerId = (int) ($args['reseller_id'] ?? 0);
                $domain = (string) ($args['domain'] ?? 'unknown');

                return [
                    'vhost_path' => "/etc/httpd/conf.d/reseller-{$resellerId}-{$domain}.conf",
                    'domain' => $domain,
                    'reseller_id' => $resellerId,
                    'reloaded' => true,
                ];
            })(),
            'reseller-vhost-remove' => (function () use ($args): array {
                $resellerId = (int) ($args['reseller_id'] ?? 0);
                $domain = (string) ($args['domain'] ?? 'unknown');

                return [
                    'vhost_path' => "/etc/httpd/conf.d/reseller-{$resellerId}-{$domain}.conf",
                    'domain' => $domain,
                    'reseller_id' => $resellerId,
                    'removed' => true,
                ];
            })(),
            'reseller-vhost-status' => (function () use ($args): array {
                $resellerId = (int) ($args['reseller_id'] ?? 0);
                $domain = (string) ($args['domain'] ?? 'unknown');
                $vhostPath = "/etc/httpd/conf.d/reseller-{$resellerId}-{$domain}.conf";

                return [
                    'vhost_path' => $vhostPath,
                    'domain' => $domain,
                    'reseller_id' => $resellerId,
                    'exists' => true,
                    'ssl_enabled' => true,
                    'apache_test_passed' => true,
                    'last_modified' => now()->subHours(random_int(1, 240))->toIso8601String(),
                ];
            })(),

            // ── File Manager (production-grade) ──────────────────────────────
            'file-tree' => (function () use ($args): array {
                $username = (string) ($args['username'] ?? 'onx_demo');
                $relPath = trim((string) ($args['path'] ?? ''), '/');
                $home = "/home/{$username}";
                $absPath = $relPath === '' ? $home : "{$home}/{$relPath}";

                // First path segment decides which fixture to serve. This
                // mirrors how the real `onx-file-tree` script reads whatever
                // is on disk — we just stub realistic content per skeleton
                // dir so the UI can be developed on Windows without the
                // sysapi backend.
                $top = explode('/', $relPath)[0] ?? '';

                $sampleSets = [
                    // Home root (no path) — shows the skeleton manifest dirs.
                    '' => [
                        ['public_html', 'dir', 4096, '0750', 'drwxr-x---'],
                        ['public_ftp', 'dir', 4096, '0755', 'drwxr-xr-x'],
                        ['mail', 'dir', 4096, '0700', 'drwx------'],
                        ['tmp', 'dir', 4096, '1777', 'drwxrwxrwt'],
                        ['logs', 'dir', 4096, '0750', 'drwxr-x---'],
                        ['ssl', 'dir', 4096, '0700', 'drwx------'],
                        ['etc', 'dir', 4096, '0750', 'drwxr-x---'],
                        ['wordpress-backups', 'dir', 4096, '0750', 'drwxr-x---'],
                        ['perl5', 'dir', 4096, '0750', 'drwxr-x---'],
                        ['.trash', 'dir', 4096, '0700', 'drwx------'],
                        ['.htpasswds', 'dir', 4096, '0700', 'drwx------'],
                        ['.wp-cli', 'dir', 4096, '0700', 'drwx------'],
                        ['.subaccounts', 'dir', 4096, '0700', 'drwx------'],
                        ['.caldav', 'dir', 4096, '0700', 'drwx------'],
                        ['.onoxsoft', 'dir', 4096, '0700', 'drwx------'],
                        ['.bash_profile', 'file', 245, '0644', '-rw-r--r--'],
                        ['.bashrc', 'file', 312, '0644', '-rw-r--r--'],
                    ],
                    'public_html' => [
                        ['index.html', 'file', 1247, '0644', '-rw-r--r--'],
                        ['wp-config.php', 'file', 4521, '0640', '-rw-r-----'],
                        ['index.php', 'file', 405, '0644', '-rw-r--r--'],
                        ['.htaccess', 'file', 768, '0644', '-rw-r--r--'],
                        ['robots.txt', 'file', 51, '0644', '-rw-r--r--'],
                        ['favicon.ico', 'file', 15086, '0644', '-rw-r--r--'],
                        ['wp-admin', 'dir', 4096, '0755', 'drwxr-xr-x'],
                        ['wp-content', 'dir', 4096, '0755', 'drwxr-xr-x'],
                        ['wp-includes', 'dir', 4096, '0755', 'drwxr-xr-x'],
                        ['cgi-bin', 'dir', 4096, '0755', 'drwxr-xr-x'],
                        ['cache', 'dir', 4096, '0775', 'drwxrwxr-x'],
                        ['uploads', 'dir', 4096, '0755', 'drwxr-xr-x'],
                        ['error_log', 'file', 31245, '0640', '-rw-r-----'],
                        ['.user.ini', 'file', 412, '0644', '-rw-r--r--'],
                    ],
                    'mail' => [
                        ['cur', 'dir', 4096, '0700', 'drwx------'],
                        ['new', 'dir', 4096, '0700', 'drwx------'],
                        ['tmp', 'dir', 4096, '0700', 'drwx------'],
                        ['.Drafts', 'dir', 4096, '0700', 'drwx------'],
                        ['.Sent', 'dir', 4096, '0700', 'drwx------'],
                        ['.Trash', 'dir', 4096, '0700', 'drwx------'],
                        ['.Junk', 'dir', 4096, '0700', 'drwx------'],
                        ['.dovecot.lda-dupes', 'file', 124, '0600', '-rw-------'],
                        ['dovecot-uidlist', 'file', 89, '0600', '-rw-------'],
                        ['dovecot-keywords', 'file', 32, '0600', '-rw-------'],
                        ['subscriptions', 'file', 41, '0600', '-rw-------'],
                    ],
                    'tmp' => [
                        ['sess_a3f9c2e1b87d6f12', 'file', 2048, '0600', '-rw-------'],
                        ['sess_d7e8a1b3c5f290e4', 'file', 1024, '0600', '-rw-------'],
                        ['upload_xY8aQ2.tmp', 'file', 524288, '0600', '-rw-------'],
                        ['analog', 'dir', 4096, '1777', 'drwxrwxrwt'],
                        ['awstats', 'dir', 4096, '1777', 'drwxrwxrwt'],
                        ['webalizer', 'dir', 4096, '1777', 'drwxrwxrwt'],
                    ],
                    'public_ftp' => [
                        ['incoming', 'dir', 4096, '0775', 'drwxrwxr-x'],
                        ['README', 'file', 256, '0644', '-rw-r--r--'],
                    ],
                    'logs' => [
                        ['access_log', 'file', 327680, '0640', '-rw-r-----'],
                        ['error_log', 'file', 12384, '0640', '-rw-r-----'],
                        ['ssl_access_log', 'file', 184320, '0640', '-rw-r-----'],
                        ['ssl_error_log', 'file', 8192, '0640', '-rw-r-----'],
                        ['access_log.1.gz', 'file', 45056, '0640', '-rw-r-----'],
                    ],
                    'ssl' => [
                        ['certs', 'dir', 4096, '0700', 'drwx------'],
                        ['keys', 'dir', 4096, '0700', 'drwx------'],
                    ],
                    'etc' => [
                        ['valiases', 'dir', 4096, '0750', 'drwxr-x---'],
                        ['vfilters', 'dir', 4096, '0750', 'drwxr-x---'],
                    ],
                    'wordpress-backups' => [
                        ['site-2026-05-01.tar.gz', 'file', 12 * 1024 * 1024, '0640', '-rw-r-----'],
                        ['site-2026-05-08.tar.gz', 'file', 13 * 1024 * 1024, '0640', '-rw-r-----'],
                    ],
                    'perl5' => [
                        ['lib', 'dir', 4096, '0750', 'drwxr-x---'],
                    ],
                    '.trash' => [
                        ['placeholder.txt', 'file', 32, '0644', '-rw-r--r--'],
                    ],
                    '.htpasswds' => [
                        ['placeholder.txt', 'file', 32, '0644', '-rw-r--r--'],
                    ],
                    '.wp-cli' => [
                        ['config.yml', 'file', 64, '0600', '-rw-------'],
                    ],
                    '.subaccounts' => [
                        ['placeholder.txt', 'file', 32, '0644', '-rw-r--r--'],
                    ],
                    '.caldav' => [
                        ['placeholder.txt', 'file', 32, '0644', '-rw-r--r--'],
                    ],
                    '.onoxsoft' => [
                        ['meta.json', 'file', 192, '0600', '-rw-------'],
                    ],
                ];

                $samples = $sampleSets[$top] ?? $sampleSets[$relPath] ?? [
                    ['placeholder.txt', 'file', 32, '0644', '-rw-r--r--'],
                ];

                // Filter hidden if requested
                $showHidden = (bool) ($args['show_hidden'] ?? true);
                if (! $showHidden) {
                    $samples = array_values(array_filter(
                        $samples,
                        static fn (array $s): bool => ! str_starts_with($s[0], '.'),
                    ));
                }

                $items = [];
                $now = time();
                foreach ($samples as $i => [$name, $type, $size, $modeOctal, $modeSym]) {
                    $items[] = [
                        'name' => $name,
                        'rel' => $relPath === '' ? $name : "{$relPath}/{$name}",
                        'type' => $type,
                        'size' => $size,
                        'mode_octal' => $modeOctal,
                        'mode_symbolic' => $modeSym,
                        'owner' => $top === 'mail' ? 'vmail' : $username,
                        'group' => match (true) {
                            $top === 'public_html' => 'apache',
                            $top === 'mail' => 'vmail',
                            default => $username,
                        },
                        'uid' => 10001,
                        'gid' => $top === 'public_html' ? 48 : 10001,
                        'mtime' => date('Y-m-d\TH:i:s\Z', $now - ($i * 3600)),
                        'atime' => date('Y-m-d\TH:i:s\Z', $now - ($i * 600)),
                        'target' => $type === 'symlink' ? "{$home}/logs/old" : null,
                    ];
                }

                return [
                    'path' => $absPath,
                    'depth' => (int) ($args['depth'] ?? 1),
                    'count' => count($items),
                    'items' => $items,
                ];
            })(),

            'file-search' => (function () use ($args): array {
                $username = (string) ($args['username'] ?? 'onx_demo');
                $home = "/home/{$username}";
                $pattern = (string) ($args['pattern'] ?? '');
                $content = (string) ($args['content'] ?? '');

                $matches = [
                    ['path' => 'public_html/wp-config.php', 'line' => 42, 'snippet' => "define('DB_NAME', '{$username}_wp');"],
                    ['path' => 'public_html/wp-content/themes/onox/functions.php', 'line' => 18, 'snippet' => "// Theme bootstrap for {$content}"],
                    ['path' => 'public_html/wp-includes/version.php', 'line' => 7, 'snippet' => "\$wp_version = '6.5.3';"],
                    ['path' => 'public_html/index.php', 'line' => 1, 'snippet' => '<?php require __DIR__ . "/wp-blog-header.php";'],
                    ['path' => 'public_html/.htaccess', 'line' => 4, 'snippet' => 'RewriteRule . /index.php [L]'],
                ];

                // Reduce to 5 mock results so callers can rely on the shape
                // without depending on exact count.
                return [
                    'username' => $username,
                    'path' => "{$home}/" . ltrim((string) ($args['path'] ?? ''), '/'),
                    'pattern' => $pattern,
                    'content' => $content,
                    'result_count' => count($matches),
                    'truncated' => false,
                    'matches' => $matches,
                ];
            })(),

            'file-copy' => (function () use ($args): array {
                $username = (string) ($args['username'] ?? 'onx_demo');
                $home = "/home/{$username}";

                return [
                    'username' => $username,
                    'src' => "{$home}/" . ltrim((string) ($args['src'] ?? ''), '/'),
                    'dst' => "{$home}/" . ltrim((string) ($args['dst'] ?? ''), '/'),
                    'is_dir' => false,
                    'bytes' => rand(1024, 4_194_304),
                    'files_count' => rand(1, 250),
                    'overwritten' => (bool) ($args['force'] ?? false),
                ];
            })(),

            'file-move' => (function () use ($args): array {
                $username = (string) ($args['username'] ?? 'onx_demo');
                $home = "/home/{$username}";

                return [
                    'username' => $username,
                    'src' => "{$home}/" . ltrim((string) ($args['src'] ?? ''), '/'),
                    'dst' => "{$home}/" . ltrim((string) ($args['dst'] ?? ''), '/'),
                    'is_dir' => false,
                    'cross_device' => false,
                    'overwritten' => (bool) ($args['force'] ?? false),
                ];
            })(),

            'file-edit' => (function () use ($args): array {
                $username = (string) ($args['username'] ?? 'onx_demo');
                $home = "/home/{$username}";
                $rel = (string) ($args['path'] ?? '');
                $abs = "{$home}/" . ltrim($rel, '/');
                $mode = (string) ($args['mode'] ?? 'read');
                $name = basename($rel);

                if ($mode === 'read') {
                    // Path-aware mock content — dev'de gerçek dosya yoksa bile
                    // editor anlamlı bir önizleme gösterir. Production'da bash
                    // gerçek dosyayı okur — bu kod yalnızca FakeSysapi'de çalışır.
                    $sampleContent = match (true) {
                        $name === 'index.html' => $this->mockIndexHtml(),
                        $name === 'index.php' => $this->mockIndexPhp($username),
                        $name === '.htaccess' => $this->mockHtaccess($username),
                        $name === 'wp-config.php' => $this->mockWpConfig($username),
                        $name === 'robots.txt' => "User-agent: *\nDisallow:\n",
                        $name === '.gitignore' => "node_modules/\nvendor/\n.env\n.DS_Store\n*.log\n",
                        $name === '.env' => "APP_NAME=Onoxsoft\nAPP_ENV=production\nDB_HOST=localhost\nDB_NAME={$username}_app\n",
                        str_ends_with($name, '.php') => "<?php\n// {$name}\necho 'Hello from {$name}';\n",
                        str_ends_with($name, '.css') => "/* {$name} */\nbody {\n    font-family: -apple-system, sans-serif;\n    margin: 0;\n    padding: 1rem;\n}\n",
                        str_ends_with($name, '.js') => "// {$name}\nconsole.log('Loaded {$name}');\n",
                        str_ends_with($name, '.json') => json_encode(['name' => $name, 'mock' => true], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n",
                        str_ends_with($name, '.html'), str_ends_with($name, '.htm') => "<!DOCTYPE html>\n<html>\n<head><meta charset=\"UTF-8\"><title>{$name}</title></head>\n<body>\n    <h1>{$name}</h1>\n</body>\n</html>\n",
                        str_ends_with($name, '.txt'), str_ends_with($name, '.md') => "# {$name}\n\nBu dosya FakeSysapi tarafından üretildi (dev mock).\n",
                        str_ends_with($name, '.xml') => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- {$name} -->\n<root />\n",
                        str_ends_with($name, '.yaml'), str_ends_with($name, '.yml') => "# {$name}\nname: {$name}\nmock: true\n",
                        default => "# {$name}\n\nBu dosya henüz oluşturulmadı veya boş.\n",
                    };

                    return [
                        'path' => $abs,
                        'size' => strlen($sampleContent),
                        'encoding' => 'utf8',
                        'binary' => false,
                        'content' => $sampleContent,
                        'mime' => $this->detectMime($name),
                        'sha256' => hash('sha256', $sampleContent),
                        'mtime' => now()->subMinutes(rand(0, 1440))->toIso8601ZuluString(),
                    ];
                }

                $written = (string) ($args['content'] ?? '');
                $ts = time();

                return [
                    'path' => $abs,
                    'bytes_written' => strlen($written),
                    'backup_path' => "{$abs}.bak-{$ts}",
                    'sha256' => hash('sha256', $written),
                    'diff' => ($args['diff'] ?? false) ? "--- {$abs}\n+++ {$abs}\n@@ -1 +1 @@\n-old\n+new" : null,
                ];
            })(),

            'zip-create' => (function () use ($args): array {
                $username = (string) ($args['username'] ?? 'onx_demo');
                $home = "/home/{$username}";
                $output = (string) ($args['output'] ?? 'archive.zip');
                $format = (string) ($args['format'] ?? 'zip');
                $abs = "{$home}/" . ltrim($output, '/');
                $size = rand(102_400, 52_428_800);

                return [
                    'archive_path' => $abs,
                    'format' => $format,
                    'size_bytes' => $size,
                    'file_count' => rand(5, 4_000),
                    'sha256' => hash('sha256', $abs . $size),
                ];
            })(),

            'zip-extract' => (function () use ($args): array {
                $username = (string) ($args['username'] ?? 'onx_demo');
                $home = "/home/{$username}";
                $archive = (string) ($args['archive'] ?? '');
                $dest = (string) ($args['dest'] ?? 'restore');
                $fn = strtolower($archive);
                $format = match (true) {
                    str_ends_with($fn, '.zip') => 'zip',
                    str_ends_with($fn, '.tar.gz'), str_ends_with($fn, '.tgz') => 'tar.gz',
                    str_ends_with($fn, '.7z') => '7z',
                    default => 'tar',
                };

                return [
                    'archive' => "{$home}/" . ltrim($archive, '/'),
                    'dest' => "{$home}/" . ltrim($dest, '/'),
                    'extracted_count' => rand(8, 3_500),
                    'format' => $format,
                    'skipped_unsafe' => 0,
                ];
            })(),

            'trash-send' => (function () use ($args): array {
                $username = (string) ($args['username'] ?? 'onx_demo');
                $home = "/home/{$username}";
                $path = (string) ($args['path'] ?? 'unknown');
                $trashId = substr(bin2hex(random_bytes(4)), 0, 8);
                $bn = basename($path);

                return [
                    'trash_id' => $trashId,
                    'original_path' => ltrim($path, '/'),
                    'trash_path' => "{$home}/.trash/{$trashId}/{$bn}",
                    'size_bytes' => rand(128, 16_777_216),
                    'deleted_at' => now()->toIso8601ZuluString(),
                ];
            })(),

            'trash-list' => (function () use ($args): array {
                $username = (string) ($args['username'] ?? 'onx_demo');
                $home = "/home/{$username}";

                $samples = [
                    ['original_path' => 'public_html/old-stuff/draft.html', 'is_dir' => false, 'size' => 2_415, 'age' => 0],
                    ['original_path' => 'logs/access.log.2026-04', 'is_dir' => false, 'size' => 1_847_392, 'age' => 5],
                    ['original_path' => 'public_html/wp-content/themes/old-theme', 'is_dir' => true, 'size' => 12_582_912, 'age' => 12],
                ];

                $items = [];
                $total = 0;
                foreach ($samples as $s) {
                    $tid = substr(bin2hex(random_bytes(4)), 0, 8);
                    $items[] = [
                        'trash_id' => $tid,
                        'original_path' => $s['original_path'],
                        'original_basename' => basename($s['original_path']),
                        'size_bytes' => $s['size'],
                        'is_dir' => $s['is_dir'],
                        'deleted_at' => now()->subDays($s['age'])->toIso8601ZuluString(),
                        'age_days' => $s['age'],
                    ];
                    $total += $s['size'];
                }

                return [
                    'username' => $username,
                    'trash_root' => "{$home}/.trash",
                    'count' => count($items),
                    'total_bytes' => $total,
                    'items' => $items,
                ];
            })(),

            'trash-empty' => (function () use ($args): array {
                $removed = rand(2, 10);

                return [
                    'username' => (string) ($args['username'] ?? 'onx_demo'),
                    'removed' => $removed,
                    'freed_bytes' => $removed * rand(1_000_000, 5_000_000),
                    'remaining' => 0,
                ];
            })(),

            'trash-restore' => (function () use ($args): array {
                $username = (string) ($args['username'] ?? 'onx_demo');
                $home = "/home/{$username}";
                $trashId = (string) ($args['trash_id'] ?? 'unknown');
                $origPath = 'public_html/restored-item.html';

                return [
                    'trash_id' => $trashId,
                    'restored_to' => "{$home}/{$origPath}",
                    'original_path' => $origPath,
                    'size_bytes' => rand(512, 1_048_576),
                    'overwritten' => (bool) ($args['overwrite'] ?? false),
                ];
            })(),

            // ── Mail — Postfix queue (mock for dev/non-Linux) ────────────────
            'postqueue-status' => (function () use ($args): array {
                $filter = (string) ($args['filter'] ?? 'all');
                $statuses = ['active', 'deferred', 'hold'];
                $reasons = ['Connection timed out', 'Greylisted', 'Mailbox full', 'IP blacklisted', null, null];
                $senderDomains = ['acme.com.tr', 'orhanblog.com', 'modashop.com.tr'];
                $rcptDomains = ['gmail.com', 'hotmail.com', 'yandex.com.tr', 'outlook.com'];

                $queue = [];
                $counts = ['active' => 0, 'deferred' => 0, 'hold' => 0];
                $bytes = 0;
                for ($i = 1; $i <= 12; $i++) {
                    $st = $statuses[array_rand($statuses)];
                    if ($filter !== 'all' && $filter !== $st) {
                        continue;
                    }
                    $size = rand(2_000, 250_000);
                    $bytes += $size;
                    $counts[$st]++;
                    $queue[] = [
                        'queue_id' => strtoupper(substr(md5((string) $i.microtime()), 0, 10)),
                        'size' => $size,
                        'arrival' => now()->subMinutes(rand(1, 720))->format('M d H:i:s'),
                        'sender' => 'noreply@'.$senderDomains[array_rand($senderDomains)],
                        'recipient' => 'user'.rand(1, 999).'@'.$rcptDomains[array_rand($rcptDomains)],
                        'reason' => $reasons[array_rand($reasons)] ?? '',
                        'status' => $st,
                    ];
                }

                return [
                    'ok' => true,
                    'count' => count($queue),
                    'filter' => $filter,
                    'totals' => [
                        'queue_size' => count($queue),
                        'active' => $counts['active'],
                        'deferred' => $counts['deferred'],
                        'hold' => $counts['hold'],
                        'bytes' => $bytes,
                    ],
                    'queue' => $queue,
                ];
            })(),
            'postqueue-flush' => [
                'ok' => true,
                'message' => 'Kuyruk flush tetiklendi (postqueue -f) — mock',
            ],
            'postqueue-delete' => [
                'ok' => true,
                'queue_id' => (string) ($args['queue_id'] ?? 'UNKNOWN'),
                'message' => 'postsuper -d tamamlandı (mock)',
            ],
            'postqueue-hold' => [
                'ok' => true,
                'queue_id' => (string) ($args['queue_id'] ?? 'UNKNOWN'),
                'message' => 'postsuper -h tamamlandı (mock)',
            ],
            'postqueue-requeue' => [
                'ok' => true,
                'queue_id' => (string) ($args['queue_id'] ?? 'UNKNOWN'),
                'message' => 'postsuper -r tamamlandı (mock)',
            ],

            // ── Mail — Rspamd DKIM stats (mock) ──────────────────────────────
            'rspamd-dkim-stats' => (function () use ($args): array {
                $limit = min(max((int) ($args['limit'] ?? 50), 1), 500);
                $domains = ['acme.com.tr', 'orhanblog.com', 'modashop.com.tr', 'neoclubkemer.com'];
                $selectors = ['dkim', 'mail', 's1', 'onx2026'];
                $actions = ['signed', 'signed', 'signed', 'signed', 'missing_key', 'verify_fail'];
                $events = [];
                for ($i = 0; $i < min(20, $limit); $i++) {
                    $events[] = [
                        'timestamp' => now()->subMinutes($i * 7)->format('Y-m-d H:i:s'),
                        'queue_id' => strtoupper(substr(md5((string) $i.'dkim'), 0, 12)),
                        'domain' => $domains[array_rand($domains)],
                        'selector' => $selectors[array_rand($selectors)],
                        'action' => $actions[array_rand($actions)],
                        'recipient' => 'user'.rand(1, 999).'@gmail.com',
                    ];
                }

                return [
                    'ok' => true,
                    'count' => count($events),
                    'events' => $events,
                ];
            })(),
            'rspamd-learn' => [
                'ok' => true,
                'mode' => (string) ($args['mode'] ?? 'spam'),
                'messages_learned' => rand(5, 80),
                'message' => 'rspamadm learn_'.($args['mode'] ?? 'spam').' tamamlandı (mock)',
            ],
            'rspamd-reload' => [
                'ok' => true,
                'message' => 'rspamadm configtest + reload (mock)',
            ],
            'rspamd-bayes-reset' => [
                'ok' => true,
                'message' => 'Bayes classifier database reset (Redis BAYES_* keys deleted)',
                'deleted_keys' => rand(100, 5000),
            ],
            'rspamd-conf-write' => [
                'ok' => true,
                'path' => '/etc/rspamd/local.d/'.($args['module'] ?? 'options').'.conf',
                'bytes' => rand(120, 2200),
            ],

            // ── Mail — DKIM (mock) ───────────────────────────────────────────
            'dkim-keygen' => (function () use ($args): array {
                $domain = (string) ($args['domain'] ?? 'example.com');
                $selector = (string) ($args['selector'] ?? 'dkim');

                return [
                    'ok' => true,
                    'domain' => $domain,
                    'selector' => $selector,
                    'public_key' => 'v=DKIM1; k=rsa; p='.base64_encode(random_bytes(180)),
                    'dns_record' => "{$selector}._domainkey.{$domain} IN TXT \"v=DKIM1; k=rsa; p=...\"",
                ];
            })(),
            'dkim-list' => [
                'ok' => true,
                'count' => 0,
                'records' => [],
            ],
            'dkim-remove' => [
                'ok' => true,
                'domain' => (string) ($args['domain'] ?? 'example.com'),
                'removed' => true,
            ],

            // ── Mail — Sieve script writer (mock) ────────────────────────────
            'sieve-write' => (function () use ($args): array {
                $email = (string) ($args['email'] ?? 'user@example.com');
                [$local, $domain] = array_pad(explode('@', $email, 2), 2, 'example.com');
                $path = "/var/vmail/{$domain}/{$local}/sieve/managesieve.sieve";
                $rules = (array) ($args['rules'] ?? []);
                $raw = (string) ($args['raw_script'] ?? '');
                $bytes = $raw !== '' ? strlen($raw) : (200 + count($rules) * 60);

                return [
                    'ok' => true,
                    'script_path' => $path,
                    'bytes' => $bytes,
                    'rules_count' => count($rules),
                    'boxtrapper' => (bool) ($args['boxtrapper'] ?? false),
                    'message' => 'Sieve script kaydedildi (mock)',
                ];
            })(),

            // FTP — aktif oturum sayısı (pure-ftpwho parse)
            'ftp-who' => [
                'sessions' => [],
                'total' => 0,
                'note' => 'pure-ftpwho: production-only — dev/Windows boş döner',
            ],

            // ── MySQL Slow Query Analyzer (Admin) ────────────────────────────
            'db-slow-queries' => $this->mockDbSlowQueries($args),
            'db-slow-queries-explain' => $this->mockDbSlowQueriesExplain($args),
            'db-slow-queries-clear' => [
                'ok' => true,
                'log_path' => '/var/lib/mysql/slow.log',
                'bytes_before' => 12_500_000,
                'bytes_after' => 0,
                'truncated_at' => now()->toIso8601String(),
                'message' => 'slow_query.log truncate edildi (mock).',
            ],

            default => $args,
        };

        return SysapiResult::success($data);
    }

    public function isReal(): bool
    {
        return false;
    }

    public function allowedActions(): array
    {
        return AllowedActions::list();
    }

    /**
     * Sıraya başarılı/başarısız bir sonuç ekle.
     * FIFO — birden fazla queueResult çağrısı sırayla tüketilir.
     */
    public function queueResult(SysapiResult $result): self
    {
        $this->queue[] = $result;

        return $this;
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function queueOk(array $data = []): self
    {
        return $this->queueResult(SysapiResult::success($data));
    }

    public function queueFail(int $exitCode = 3, string $stderr = 'Mock failure'): self
    {
        return $this->queueResult(SysapiResult::fail($exitCode, $stderr));
    }

    // ─── Assertions ───────────────────────────────────────────────────────

    public function assertCalled(string $command, ?int $times = null): void
    {
        $count = count(array_filter($this->calls, fn ($c) => $c['command'] === $command));

        if ($times === null) {
            Assert::assertGreaterThan(0, $count, "Sysapi command '{$command}' beklendi ama çağrılmadı");
        } else {
            Assert::assertSame($times, $count, "Sysapi command '{$command}' {$times} kere çağrılmalıydı, {$count} kere çağrıldı");
        }
    }

    public function assertNotCalled(string $command): void
    {
        $count = count(array_filter($this->calls, fn ($c) => $c['command'] === $command));
        Assert::assertSame(0, $count, "Sysapi command '{$command}' çağrılmamalıydı ama {$count} kere çağrıldı");
    }

    /**
     * Subset match: beklenen tüm anahtarlar mevcut çağrıda aynı değerle varsa OK.
     * Ek anahtarların olması başarısızlığa yol açmaz — bu sayede provisioner'a
     * yeni opsiyonel argümanlar eklemek mevcut testleri kırmaz.
     *
     * @param  array<string, mixed>  $expectedArgs
     */
    public function assertCalledWith(string $command, array $expectedArgs): void
    {
        foreach ($this->calls as $call) {
            if ($call['command'] !== $command) {
                continue;
            }
            // Strict eşleşme öncelikli — backward compat
            if ($call['args'] == $expectedArgs) {
                Assert::assertTrue(true);

                return;
            }
            // Subset: beklenen tüm anahtarlar callArgs içinde aynı değerde var mı?
            $matches = true;
            foreach ($expectedArgs as $key => $value) {
                if (! array_key_exists($key, $call['args']) || $call['args'][$key] != $value) {
                    $matches = false;
                    break;
                }
            }
            if ($matches) {
                Assert::assertTrue(true);

                return;
            }
        }

        $actual = json_encode(array_filter($this->calls, fn ($c) => $c['command'] === $command), JSON_PRETTY_PRINT);
        $expected = json_encode($expectedArgs, JSON_PRETTY_PRINT);
        Assert::fail("Sysapi command '{$command}' beklenen args ile çağrılmadı.\nBeklenen subset: {$expected}\nGerçek çağrılar: {$actual}");
    }

    public function reset(): void
    {
        $this->queue = [];
        $this->calls = [];
    }

    // ─── Mock content helpers (file-edit read action) ──────────────────────
    // Dev'de gerçek dosya yoksa bile editor anlamlı bir önizleme gösterir;
    // production'da bash gerçek dosyayı okur, bu metotlar çağrılmaz.

    private function mockIndexHtml(): string
    {
        return <<<HTML
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Yakında!</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
        }
        .container { text-align: center; padding: 2rem; max-width: 600px; }
        h1 { font-size: 3rem; margin-bottom: 1rem; font-weight: 700; }
        p { font-size: 1.25rem; opacity: 0.9; margin-bottom: 0.5rem; }
        .badge {
            display: inline-block;
            margin-top: 2rem;
            padding: 0.5rem 1rem;
            background: rgba(255,255,255,0.2);
            border-radius: 999px;
            font-size: 0.875rem;
            backdrop-filter: blur(10px);
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Yakında!</h1>
        <p>Sitenin yapım aşamasında olduğunu görmenize sevindik.</p>
        <p>Pek yakında siz değerli ziyaretçilerimizi karşılayacağız.</p>
        <div class="badge">Powered by ONOXSOFT Panel</div>
    </div>
</body>
</html>

HTML;
    }

    private function mockIndexPhp(string $username): string
    {
        return <<<PHP
<?php
/**
 * Default landing page — {$username}
 *
 * Bu dosyayı düzenleyerek sitenizi yayına alabilirsiniz.
 */

\$title = 'Yakında!';
\$message = 'Sitenin yapım aşamasında olduğunu görmenize sevindik.';
?>
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <title><?= htmlspecialchars(\$title) ?></title>
</head>
<body>
    <h1><?= htmlspecialchars(\$title) ?></h1>
    <p><?= htmlspecialchars(\$message) ?></p>
</body>
</html>

PHP;
    }

    private function mockHtaccess(string $username): string
    {
        return <<<TEXT
# Onoxsoft default .htaccess — basic security + perf
# Customer kuralları için .htaccess.local kullanın.

Options -Indexes
ServerSignature Off

# Hidden dosyaları (.git, .env, vd.) blokla
<FilesMatch "^\.">
    Require all denied
</FilesMatch>

# Statik içerik için cache header'ları
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/jpeg "access plus 1 month"
    ExpiresByType image/png "access plus 1 month"
    ExpiresByType image/webp "access plus 1 month"
    ExpiresByType text/css "access plus 1 week"
    ExpiresByType application/javascript "access plus 1 week"
</IfModule>

# Customer-tarafı override: HtaccessProvisioner yönetir
# IncludeOptional /home/{$username}/public_html/.htaccess.local

TEXT;
    }

    private function mockWpConfig(string $username): string
    {
        return <<<PHP
<?php
/**
 * WordPress Configuration — MOCK
 *
 * Üretimde gerçek değerler `cpanel` kurulumu sırasında yazılır.
 * Bu dosya yalnızca dev preview için.
 */

define( 'DB_NAME', '{$username}_wp' );
define( 'DB_USER', '{$username}_wp' );
define( 'DB_PASSWORD', '***MOCK***' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );

\$table_prefix = 'wp_';

define( 'WP_DEBUG', false );
define( 'DISALLOW_FILE_EDIT', true );

if ( ! defined( 'ABSPATH' ) ) {
    define( 'ABSPATH', __DIR__ . '/' );
}

require_once ABSPATH . 'wp-settings.php';

PHP;
    }

    /**
     * Slow query log parser mock — pt-query-digest benzeri gruplama.
     *
     * @param  array<string, mixed>  $args
     * @return array<string, mixed>
     */
    private function mockDbSlowQueries(array $args): array
    {
        $limit = (int) ($args['limit'] ?? 50);
        $minCount = (int) ($args['min_count'] ?? 0);
        $minAvgTime = (float) ($args['min_avg_time'] ?? 0);
        $dbFilter = (string) ($args['database'] ?? '');
        $userFilter = (string) ($args['user'] ?? '');

        $queries = [
            [
                'hash' => 'a1b2c3d4',
                'normalized' => 'SELECT * FROM users WHERE email = ?',
                'sample' => 'SELECT * FROM users WHERE email = "demo@example.com"',
                'count' => 247,
                'avg_time' => 3.2,
                'max_time' => 8.7,
                'total_time' => 791.4,
                'rows_examined_avg' => 50000,
                'rows_sent_avg' => 1,
                'first_seen' => '2026-05-15 03:21:00',
                'last_seen' => '2026-05-17 11:42:01',
                'databases' => ['onx_demo01_app'],
                'users' => ['onx_demo01_user'],
            ],
            [
                'hash' => 'e5f6a7b8',
                'normalized' => 'SELECT p.*, u.name FROM posts p JOIN users u ON u.id = p.user_id WHERE p.status = ? ORDER BY p.created_at DESC',
                'sample' => "SELECT p.*, u.name FROM posts p JOIN users u ON u.id = p.user_id WHERE p.status = 'published' ORDER BY p.created_at DESC",
                'count' => 189,
                'avg_time' => 4.8,
                'max_time' => 12.3,
                'total_time' => 907.2,
                'rows_examined_avg' => 120000,
                'rows_sent_avg' => 50,
                'first_seen' => '2026-05-14 10:15:00',
                'last_seen' => '2026-05-17 11:58:03',
                'databases' => ['onx_demo01_app'],
                'users' => ['onx_demo01_user'],
            ],
            [
                'hash' => 'c9d0e1f2',
                'normalized' => 'SELECT * FROM products WHERE name LIKE ?',
                'sample' => 'SELECT * FROM products WHERE name LIKE "%kalem%"',
                'count' => 412,
                'avg_time' => 2.1,
                'max_time' => 6.5,
                'total_time' => 865.2,
                'rows_examined_avg' => 85000,
                'rows_sent_avg' => 12,
                'first_seen' => '2026-05-13 22:01:00',
                'last_seen' => '2026-05-17 11:50:21',
                'databases' => ['onx_shop01_db'],
                'users' => ['onx_shop01_user'],
            ],
            [
                'hash' => '3a4b5c6d',
                'normalized' => 'UPDATE sessions SET last_activity = ? WHERE id = ?',
                'sample' => 'UPDATE sessions SET last_activity = 1715937721 WHERE id = "abc123def456"',
                'count' => 1842,
                'avg_time' => 2.4,
                'max_time' => 5.1,
                'total_time' => 4420.8,
                'rows_examined_avg' => 35000,
                'rows_sent_avg' => 0,
                'first_seen' => '2026-05-10 00:00:00',
                'last_seen' => '2026-05-17 11:59:55',
                'databases' => ['onx_demo01_app'],
                'users' => ['onx_demo01_user'],
            ],
            [
                'hash' => '7e8f9a0b',
                'normalized' => 'SELECT COUNT(*) FROM orders WHERE created_at BETWEEN ? AND ?',
                'sample' => "SELECT COUNT(*) FROM orders WHERE created_at BETWEEN '2026-01-01' AND '2026-05-17'",
                'count' => 73,
                'avg_time' => 5.9,
                'max_time' => 11.2,
                'total_time' => 430.7,
                'rows_examined_avg' => 230000,
                'rows_sent_avg' => 1,
                'first_seen' => '2026-05-12 08:45:00',
                'last_seen' => '2026-05-17 09:30:14',
                'databases' => ['onx_shop01_db'],
                'users' => ['onx_shop01_user'],
            ],
            [
                'hash' => '1c2d3e4f',
                'normalized' => 'SELECT * FROM logs WHERE level = ? AND created_at > ?',
                'sample' => "SELECT * FROM logs WHERE level = 'error' AND created_at > '2026-05-10'",
                'count' => 312,
                'avg_time' => 3.7,
                'max_time' => 9.4,
                'total_time' => 1154.4,
                'rows_examined_avg' => 95000,
                'rows_sent_avg' => 8,
                'first_seen' => '2026-05-11 06:20:00',
                'last_seen' => '2026-05-17 11:12:00',
                'databases' => ['onx_app02_db'],
                'users' => ['onx_app02_user'],
            ],
            [
                'hash' => '5g6h7i8j',
                'normalized' => 'SELECT * FROM wp_options WHERE autoload = ?',
                'sample' => "SELECT * FROM wp_options WHERE autoload = 'yes'",
                'count' => 558,
                'avg_time' => 2.6,
                'max_time' => 7.2,
                'total_time' => 1450.8,
                'rows_examined_avg' => 45000,
                'rows_sent_avg' => 320,
                'first_seen' => '2026-05-09 11:30:00',
                'last_seen' => '2026-05-17 11:45:00',
                'databases' => ['onx_blog01_wp'],
                'users' => ['onx_blog01_user'],
            ],
            [
                'hash' => '9k0l1m2n',
                'normalized' => 'DELETE FROM cache WHERE expires_at < ?',
                'sample' => "DELETE FROM cache WHERE expires_at < '2026-05-17 00:00:00'",
                'count' => 24,
                'avg_time' => 6.4,
                'max_time' => 14.8,
                'total_time' => 153.6,
                'rows_examined_avg' => 180000,
                'rows_sent_avg' => 0,
                'first_seen' => '2026-05-14 02:00:00',
                'last_seen' => '2026-05-17 02:00:01',
                'databases' => ['onx_demo01_app'],
                'users' => ['onx_demo01_user'],
            ],
            [
                'hash' => 'o3p4q5r6',
                'normalized' => 'SELECT t.*, c.name FROM tickets t LEFT JOIN customers c ON c.id = t.customer_id WHERE t.priority IN (?, ?, ?)',
                'sample' => "SELECT t.*, c.name FROM tickets t LEFT JOIN customers c ON c.id = t.customer_id WHERE t.priority IN ('high', 'urgent', 'critical')",
                'count' => 96,
                'avg_time' => 4.1,
                'max_time' => 10.9,
                'total_time' => 393.6,
                'rows_examined_avg' => 78000,
                'rows_sent_avg' => 42,
                'first_seen' => '2026-05-13 14:00:00',
                'last_seen' => '2026-05-17 10:22:00',
                'databases' => ['onx_crm01_db'],
                'users' => ['onx_crm01_user'],
            ],
            [
                'hash' => 's7t8u9v0',
                'normalized' => 'SELECT SUM(amount) FROM transactions WHERE user_id = ? AND status = ?',
                'sample' => "SELECT SUM(amount) FROM transactions WHERE user_id = 1024 AND status = 'completed'",
                'count' => 421,
                'avg_time' => 2.9,
                'max_time' => 8.0,
                'total_time' => 1220.9,
                'rows_examined_avg' => 62000,
                'rows_sent_avg' => 1,
                'first_seen' => '2026-05-11 09:00:00',
                'last_seen' => '2026-05-17 11:30:00',
                'databases' => ['onx_shop01_db'],
                'users' => ['onx_shop01_user'],
            ],
            [
                'hash' => 'w1x2y3z4',
                'normalized' => 'SELECT * FROM comments WHERE post_id = ? ORDER BY created_at',
                'sample' => 'SELECT * FROM comments WHERE post_id = 88241 ORDER BY created_at',
                'count' => 156,
                'avg_time' => 2.2,
                'max_time' => 5.8,
                'total_time' => 343.2,
                'rows_examined_avg' => 28000,
                'rows_sent_avg' => 18,
                'first_seen' => '2026-05-12 16:00:00',
                'last_seen' => '2026-05-17 11:00:00',
                'databases' => ['onx_blog01_wp'],
                'users' => ['onx_blog01_user'],
            ],
            [
                'hash' => 'b5c6d7e8',
                'normalized' => 'INSERT INTO audit_log (user_id, action, payload, created_at) VALUES (?, ?, ?, ?)',
                'sample' => "INSERT INTO audit_log (user_id, action, payload, created_at) VALUES (1, 'login', '{...}', NOW())",
                'count' => 38,
                'avg_time' => 2.0,
                'max_time' => 4.1,
                'total_time' => 76.0,
                'rows_examined_avg' => 0,
                'rows_sent_avg' => 0,
                'first_seen' => '2026-05-15 09:00:00',
                'last_seen' => '2026-05-17 11:20:00',
                'databases' => ['onx_app02_db'],
                'users' => ['onx_app02_user'],
            ],
        ];

        // Apply filters
        $filtered = array_values(array_filter($queries, function ($q) use ($minCount, $minAvgTime, $dbFilter, $userFilter) {
            if ($q['count'] < $minCount) {
                return false;
            }
            if ($q['avg_time'] < $minAvgTime) {
                return false;
            }
            if ($dbFilter !== '' && ! in_array($dbFilter, $q['databases'], true)) {
                return false;
            }
            if ($userFilter !== '' && ! in_array($userFilter, $q['users'], true)) {
                return false;
            }
            return true;
        }));

        return [
            'log_path' => '/var/lib/mysql/slow.log',
            'log_size_bytes' => 12_500_000,
            'enabled' => true,
            'long_query_time' => 2.0,
            'queries' => array_slice($filtered, 0, $limit),
            'total_distinct' => count($filtered),
        ];
    }

    /**
     * EXPLAIN + suggestion mock — hash veya raw query'e bağlı.
     *
     * @param  array<string, mixed>  $args
     * @return array<string, mixed>
     */
    private function mockDbSlowQueriesExplain(array $args): array
    {
        $query = trim((string) ($args['query'] ?? ''));
        $hash = (string) ($args['hash'] ?? '');

        // Sentinel: LIKE leading wildcard → no index possible
        $lower = strtolower($query.' '.$hash);
        $isLike = str_contains($lower, 'like') && str_contains($lower, '%kalem');
        $isJoin = str_contains($lower, 'join') || $hash === 'e5f6a7b8' || $hash === 'o3p4q5r6';
        $isUpdate = str_starts_with($lower, 'update') || $hash === '3a4b5c6d';
        $isCount = str_contains($lower, 'count(') || $hash === '7e8f9a0b';

        if ($isLike) {
            return [
                'query' => $query !== '' ? $query : 'SELECT * FROM products WHERE name LIKE "%kalem%"',
                'explain' => [
                    [
                        'id' => 1, 'select_type' => 'SIMPLE', 'table' => 'products', 'type' => 'ALL',
                        'possible_keys' => null, 'key' => null, 'key_len' => null, 'ref' => null,
                        'rows' => 85000, 'Extra' => 'Using where',
                    ],
                ],
                'suggestions' => [
                    'LIKE "%..." (leading wildcard) — B-tree index kullanılamaz. FULLTEXT index ekleyin: ALTER TABLE products ADD FULLTEXT KEY ft_name (name);',
                    'Alternatif: name kolonuna trigram veya prefix-based arama (ngram_token_size) ekleyin.',
                    'Eğer arama prefix ise (name LIKE "kalem%"), normal index iş görür: CREATE INDEX idx_products_name ON products(name);',
                ],
            ];
        }

        if ($isJoin) {
            return [
                'query' => $query !== '' ? $query : 'SELECT p.*, u.name FROM posts p JOIN users u ON u.id = p.user_id WHERE p.status = "published" ORDER BY p.created_at DESC',
                'explain' => [
                    [
                        'id' => 1, 'select_type' => 'SIMPLE', 'table' => 'p', 'type' => 'ALL',
                        'possible_keys' => null, 'key' => null, 'key_len' => null, 'ref' => null,
                        'rows' => 120000, 'Extra' => 'Using where; Using filesort',
                    ],
                    [
                        'id' => 1, 'select_type' => 'SIMPLE', 'table' => 'u', 'type' => 'eq_ref',
                        'possible_keys' => 'PRIMARY', 'key' => 'PRIMARY', 'key_len' => '4', 'ref' => 'p.user_id',
                        'rows' => 1, 'Extra' => null,
                    ],
                ],
                'suggestions' => [
                    'posts.status üzerinde index yok — CREATE INDEX idx_posts_status_created ON posts(status, created_at DESC);',
                    'Using filesort tespit edildi — ORDER BY created_at için composite index (status, created_at) filesort\'u eler.',
                    'posts.user_id üzerinde FK index yoksa ekleyin: ALTER TABLE posts ADD INDEX idx_posts_user (user_id);',
                ],
            ];
        }

        if ($isUpdate) {
            return [
                'query' => $query !== '' ? $query : 'UPDATE sessions SET last_activity = 1715937721 WHERE id = "abc123def456"',
                'explain' => [
                    [
                        'id' => 1, 'select_type' => 'UPDATE', 'table' => 'sessions', 'type' => 'ALL',
                        'possible_keys' => null, 'key' => null, 'key_len' => null, 'ref' => null,
                        'rows' => 35000, 'Extra' => 'Using where',
                    ],
                ],
                'suggestions' => [
                    'sessions.id PRIMARY KEY değil — CHAR/VARCHAR id ise PRIMARY KEY ekleyin: ALTER TABLE sessions ADD PRIMARY KEY (id);',
                    'Her UPDATE 35K satır tarıyor — yüksek frekansta çalıştığı için en kritik darboğaz.',
                ],
            ];
        }

        if ($isCount) {
            return [
                'query' => $query !== '' ? $query : "SELECT COUNT(*) FROM orders WHERE created_at BETWEEN '2026-01-01' AND '2026-05-17'",
                'explain' => [
                    [
                        'id' => 1, 'select_type' => 'SIMPLE', 'table' => 'orders', 'type' => 'ALL',
                        'possible_keys' => null, 'key' => null, 'key_len' => null, 'ref' => null,
                        'rows' => 230000, 'Extra' => 'Using where',
                    ],
                ],
                'suggestions' => [
                    'created_at üzerinde range scan index yok — CREATE INDEX idx_orders_created ON orders(created_at);',
                    'Aggregate cache: COUNT(*) sonuçlarını günlük cron ile materialized view\'a yazın.',
                ],
            ];
        }

        // Default — generic SELECT * WHERE
        return [
            'query' => $query !== '' ? $query : 'SELECT * FROM users WHERE email = "demo@example.com"',
            'explain' => [
                [
                    'id' => 1, 'select_type' => 'SIMPLE', 'table' => 'users', 'type' => 'ALL',
                    'possible_keys' => null, 'key' => null, 'key_len' => null, 'ref' => null,
                    'rows' => 50000, 'Extra' => 'Using where',
                ],
            ],
            'suggestions' => [
                'users.email üzerinde index yok — CREATE INDEX idx_users_email ON users(email);',
                'SELECT * yerine sadece gereken kolonları seçin (SELECT id, name, email FROM users).',
            ],
        ];
    }

    private function detectMime(string $name): string
    {
        $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));

        return match ($ext) {
            'html', 'htm' => 'text/html',
            'css' => 'text/css',
            'js', 'mjs' => 'application/javascript',
            'json' => 'application/json',
            'php' => 'application/x-php',
            'xml' => 'application/xml',
            'yaml', 'yml' => 'application/x-yaml',
            'md', 'markdown' => 'text/markdown',
            'txt', 'log', 'ini', 'conf' => 'text/plain',
            'png' => 'image/png',
            'jpg', 'jpeg' => 'image/jpeg',
            'gif' => 'image/gif',
            'webp' => 'image/webp',
            'svg' => 'image/svg+xml',
            default => 'application/octet-stream',
        };
    }
}
