<?php

namespace App\Services;

use App\Models\Backup;
use App\Models\Mikrotik;
use App\Models\Olt;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Throwable;

class SystemHealthService
{
    public const HEARTBEAT_KEY = 'system:scheduler:last_heartbeat';

    public function snapshot(): array
    {
        $checks = [
            $this->databaseCheck(),
            $this->cacheCheck(),
            $this->schedulerCheck(),
            $this->queueCheck(),
            $this->backupCheck(),
            $this->storageCheck(),
            $this->environmentCheck(),
        ];

        $rank = ['healthy' => 0, 'warning' => 1, 'critical' => 2];
        $overall = 'healthy';

        foreach ($checks as $check) {
            if ($rank[$check['status']] > $rank[$overall]) {
                $overall = $check['status'];
            }
        }

        return [
            'overall' => $overall,
            'checked_at' => now(),
            'checks' => $checks,
            'metrics' => $this->deviceMetrics(),
        ];
    }

    private function databaseCheck(): array
    {
        try {
            $startedAt = microtime(true);
            DB::select('select 1');

            return $this->check('database', 'Database', 'healthy', 'Database connection is working.', round((microtime(true) - $startedAt) * 1000).' ms');
        } catch (Throwable) {
            return $this->check('database', 'Database', 'critical', 'Database connection failed.');
        }
    }

    private function cacheCheck(): array
    {
        try {
            $key = 'system:health:'.str()->uuid();
            Cache::put($key, 'ok', 15);
            $working = Cache::get($key) === 'ok';
            Cache::forget($key);

            return $working
                ? $this->check('cache', 'Cache', 'healthy', 'Cache read and write are working.')
                : $this->check('cache', 'Cache', 'critical', 'Cache value could not be read back.');
        } catch (Throwable) {
            return $this->check('cache', 'Cache', 'critical', 'Cache connection failed.');
        }
    }

    private function schedulerCheck(): array
    {
        try {
            $value = Cache::get(self::HEARTBEAT_KEY);
            $lastHeartbeat = $value ? now()->parse($value) : null;

            if (! $lastHeartbeat) {
                return $this->check('scheduler', 'Scheduler', 'warning', 'No scheduler heartbeat has been recorded yet.');
            }

            $age = $lastHeartbeat->diffInMinutes(now());
            $status = $age <= 3 ? 'healthy' : ($age <= 10 ? 'warning' : 'critical');

            return $this->check(
                'scheduler',
                'Scheduler',
                $status,
                $status === 'healthy' ? 'Scheduler is running.' : 'Scheduler heartbeat is delayed.',
                $lastHeartbeat->diffForHumans()
            );
        } catch (Throwable) {
            return $this->check('scheduler', 'Scheduler', 'critical', 'Scheduler heartbeat cannot be read.');
        }
    }

    private function queueCheck(): array
    {
        try {
            if (! Schema::hasTable('jobs')) {
                return $this->check('queue', 'Queue', 'warning', 'Queue table is not available.');
            }

            $pending = DB::table('jobs')->count();
            $failed = Schema::hasTable('failed_jobs') ? DB::table('failed_jobs')->count() : 0;
            $recentFailed = Schema::hasTable('failed_jobs')
                ? DB::table('failed_jobs')->where('failed_at', '>=', now()->subDay())->count()
                : 0;
            $oldest = DB::table('jobs')->min('created_at');
            $oldestAt = $oldest ? Carbon::createFromTimestamp((int) $oldest) : null;
            $clockSkew = $oldestAt?->isAfter(now()->addMinute()) ?? false;
            $oldestMinutes = $oldestAt && ! $clockSkew
                ? max(0, (int) $oldestAt->diffInMinutes(now()))
                : 0;
            $status = $recentFailed > 0 || $oldestMinutes > 10
                ? 'critical'
                : (($failed > 0 || $clockSkew || $pending > 100 || $oldestMinutes > 3) ? 'warning' : 'healthy');

            return $this->check(
                'queue',
                'Queue',
                $status,
                "{$pending} pending, {$recentFailed} recent / {$failed} total failed job(s).",
                $clockSkew ? 'Queue/server clock mismatch' : ($oldest ? "Oldest: {$oldestMinutes} min" : 'No backlog')
            );
        } catch (Throwable) {
            return $this->check('queue', 'Queue', 'critical', 'Queue status could not be checked.');
        }
    }

    private function backupCheck(): array
    {
        try {
            if (! Schema::hasTable('backups')) {
                return $this->check('backup', 'Backup', 'warning', 'Backup history is not available.');
            }

            $latest = Backup::query()->latest()->first();
            if (! $latest) {
                return $this->check('backup', 'Backup', 'warning', 'No database backup has been created yet.');
            }

            $age = $latest->created_at->diffInHours(now());
            $successful = in_array(strtolower((string) $latest->status), ['completed', 'success', 'successful'], true);
            $status = ! $successful ? 'critical' : ($age > 48 ? 'warning' : 'healthy');

            return $this->check('backup', 'Backup', $status, $successful ? 'Latest backup completed.' : 'Latest backup did not complete.', $latest->created_at->diffForHumans());
        } catch (Throwable) {
            return $this->check('backup', 'Backup', 'warning', 'Backup status could not be checked.');
        }
    }

    private function storageCheck(): array
    {
        $path = storage_path();
        $writable = is_writable($path);
        $free = @disk_free_space($path);
        $total = @disk_total_space($path);
        $percentFree = $free !== false && $total ? ($free / $total) * 100 : null;
        $status = ! $writable || ($percentFree !== null && $percentFree < 5)
            ? 'critical'
            : (($percentFree !== null && $percentFree < 15) ? 'warning' : 'healthy');
        $meta = $free === false ? null : $this->formatBytes((float) $free).' free';

        return $this->check('storage', 'Storage', $status, $writable ? 'Storage is writable.' : 'Storage is not writable.', $meta);
    }

    private function environmentCheck(): array
    {
        $productionDebug = app()->environment('production') && config('app.debug');

        return $this->check(
            'environment',
            'Environment',
            $productionDebug ? 'critical' : 'healthy',
            $productionDebug ? 'APP_DEBUG must be disabled in production.' : 'Environment safety setting is valid.',
            app()->environment()
        );
    }

    private function deviceMetrics(): array
    {
        return [
            'mikrotik_total' => $this->safeCount(Mikrotik::class),
            'mikrotik_online' => $this->safeCount(Mikrotik::class, ['status' => true, 'connected' => true]),
            'olt_total' => $this->safeCount(Olt::class),
            'olt_online' => $this->safeCount(Olt::class, ['status' => true, 'connected' => true]),
        ];
    }

    private function safeCount(string $model, array $conditions = []): int
    {
        try {
            if (! Schema::hasTable((new $model)->getTable())) {
                return 0;
            }

            return $model::query()->where($conditions)->count();
        } catch (Throwable) {
            return 0;
        }
    }

    private function check(string $key, string $label, string $status, string $message, ?string $meta = null): array
    {
        return compact('key', 'label', 'status', 'message', 'meta');
    }

    private function formatBytes(float $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $power = $bytes > 0 ? min((int) floor(log($bytes, 1024)), count($units) - 1) : 0;

        return number_format($bytes / (1024 ** $power), $power > 1 ? 1 : 0).' '.$units[$power];
    }
}
