<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Domain\Account\Events\AccountResourceLimitHit;
use App\Domain\Account\Models\Account;
use App\Domain\Resource\Services\CgroupManager;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Throwable;

/**
 * Sample cgroup usage for every active account and persist into resource_history.
 *
 * Scheduled every 5 minutes. Each row is one sample point used by the
 * Stats → Resources page to render the 24h CPU / memory / IO chart.
 */
class ResourceHistorySnapshotCommand extends Command
{
    protected $signature = 'onoxsoft:resources:snapshot
        {--account= : Only snapshot one account by username}
        {--dry-run : Don\'t persist; just print counts}';

    protected $description = 'cgroup-read tüm aktif hesaplar için snapshot al ve resource_history\'ye yaz';

    public function handle(CgroupManager $cgroups): int
    {
        $query = Account::query()->where('status', 'active');

        $username = $this->option('account');
        if (is_string($username) && $username !== '') {
            $query->where('username', $username);
        }

        $accounts = $query->get();
        if ($accounts->isEmpty()) {
            $this->info('Hiç aktif hesap yok — atlandı.');

            return self::SUCCESS;
        }

        $start = microtime(true);
        $written = 0;
        $errors = 0;
        $rows = [];

        $faultEvents = [];   // Snapshot sonrası dispatch — DB insert sırasında event sırası tutmasın

        foreach ($accounts as $account) {
            try {
                $usage = $cgroups->readUsageFor($account);

                // memory_used_mb comes back as null on FakeSysapi — skip those silently
                if (($usage['memory_used_mb'] ?? null) === null && ($usage['cpu_pct'] ?? null) === null) {
                    continue;
                }

                $cpuPct = (float) ($usage['cpu_pct'] ?? 0);
                $memMb = (int) ($usage['memory_used_mb'] ?? 0);
                $procCount = (int) ($usage['processes'] ?? 0);

                $rows[] = [
                    'account_id' => $account->id,
                    'cpu_percent' => $cpuPct,
                    'memory_bytes' => $memMb * 1024 * 1024,
                    'disk_io_read_bytes' => (int) ($usage['io_read_bytes'] ?? 0),
                    'disk_io_write_bytes' => (int) ($usage['io_write_bytes'] ?? 0),
                    'network_in_bytes' => (int) ($usage['network_in_bytes'] ?? 0),
                    'network_out_bytes' => (int) ($usage['network_out_bytes'] ?? 0),
                    'process_count' => $procCount,
                    'recorded_at' => now(),
                    'created_at' => now(),
                ];
                $written++;

                // Threshold check — %95+ aşımda AccountResourceLimitHit event'i.
                // Listener'lar bunu dinleyip mail/push notification gönderir + audit log yazar.
                $cpuLimit = (int) ($account->package?->cpu_limit_percent ?? 100);
                $memLimit = (int) ($account->package?->memory_limit_mb ?? 1024);
                $procLimit = (int) ($account->package?->entry_processes_limit ?? 50);

                if ($cpuPct >= $cpuLimit * 0.95) {
                    $faultEvents[] = new AccountResourceLimitHit($account, 'cpu', $cpuLimit, (int) $cpuPct);
                }
                if ($memMb >= $memLimit * 0.95) {
                    $faultEvents[] = new AccountResourceLimitHit($account, 'memory', $memLimit, $memMb);
                }
                if ($procLimit > 0 && $procCount >= $procLimit * 0.95) {
                    $faultEvents[] = new AccountResourceLimitHit($account, 'processes', $procLimit, $procCount);
                }
            } catch (Throwable $e) {
                $errors++;
                $this->warn("[{$account->username}] cgroup-read hatası: {$e->getMessage()}");
            }
        }

        if ($this->option('dry-run')) {
            $this->info(sprintf('Dry-run: %d row hazır, yazılmadı.', count($rows)));

            return self::SUCCESS;
        }

        if ($rows !== []) {
            // Chunk to keep insert query size reasonable on big servers
            foreach (array_chunk($rows, 500) as $chunk) {
                DB::table('resource_history')->insert($chunk);
            }
        }

        // Fire threshold events — listener'lar (mail + push) async kuyruğa gider.
        // Idempotency: AccountResourceLimitListener kendi tarafında "son N dk'da bu
        // hesap+fault için event geldi mi" kontrolü yapmalı (yoksa spam) — şimdilik
        // event her snapshot'ta fırlatılır.
        foreach ($faultEvents as $ev) {
            try {
                event($ev);
            } catch (Throwable $e) {
                $this->warn("Event dispatch error: {$e->getMessage()}");
            }
        }

        $dur = round(microtime(true) - $start, 2);
        $this->info(sprintf(
            'Done — %d sample yazıldı, %d hata, %d fault event (%.2fs)',
            $written, $errors, count($faultEvents), $dur
        ));

        return $errors > 0 ? self::FAILURE : self::SUCCESS;
    }
}
