<?php

/**
 * ============================================================
 *
 * @project   biiproject
 *
 * @author    Andika Debi Putra
 *
 * @email     andikadebiputra@gmail.com
 *
 * @website   https://biiproject.com
 *
 * @copyright Copyright (c) 2026 Andika Debi Putra
 * @license   Proprietary - All Rights Reserved
 *
 * @version   1.0.0
 *
 * @created   2026-05-01
 * ============================================================
 *
 * Unauthorized copying, modification, distribution, or use
 * of this file is strictly prohibited without prior written
 * permission from the author.
 * ============================================================
 */

namespace App\Services\Monitoring;

use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Storage;

class SystemMonitoringService
{
    // ========================
    // CPU Usage
    // ========================
    public function getCpuUsage()
    {
        if (PHP_OS_FAMILY === 'Windows') {
            $output = shell_exec('wmic cpu get loadpercentage /value');
            if (! $output) {
                return 0;
            }

            preg_match('/LoadPercentage=(\d+)/', $output, $matches);

            return isset($matches[1]) ? (int) $matches[1] : 0;
        }

        // Linux / Unix
        $load = sys_getloadavg()[0] ?? 0;
        $cores = (int) shell_exec('nproc') ?: 1;

        return (int) round(($load / $cores) * 100);
    }

    // ========================
    // RAM Usage
    // ========================
    public function getRamUsage()
    {
        return $this->getFormattedRamUsage()['percentage'];
    }

    public function getFormattedRamUsage()
    {
        if (PHP_OS_FAMILY === 'Windows') {
            $output = shell_exec('wmic OS get FreePhysicalMemory,TotalVisibleMemorySize /Value');
            if ($output) {
                preg_match('/TotalVisibleMemorySize=(\d+)/', $output, $total);
                preg_match('/FreePhysicalMemory=(\d+)/', $output, $free);
                if (isset($total[1], $free[1])) {
                    $totalBytes = (float) $total[1] * 1024;
                    $freeBytes = (float) $free[1] * 1024;
                    $usedBytes = $totalBytes - $freeBytes;

                    return [
                        'total' => MonitoringFormatter::bytes($totalBytes),
                        'free' => MonitoringFormatter::bytes($freeBytes),
                        'used' => MonitoringFormatter::bytes($usedBytes),
                        'percentage' => (int) round(($usedBytes / $totalBytes) * 100),
                    ];
                }
            }
        } else {
            $info = @file_get_contents('/proc/meminfo');
            if ($info) {
                preg_match('/MemTotal:\s+(\d+)/', $info, $total);
                preg_match('/MemAvailable:\s+(\d+)/', $info, $available);
                preg_match('/SwapTotal:\s+(\d+)/', $info, $swapTotal);
                preg_match('/SwapFree:\s+(\d+)/', $info, $swapFree);

                if (isset($total[1], $available[1])) {
                    $totalBytes = (float) $total[1] * 1024;
                    $availableBytes = (float) $available[1] * 1024;
                    $usedBytes = $totalBytes - $availableBytes;

                    $swapTotalBytes = isset($swapTotal[1]) ? (float) $swapTotal[1] * 1024 : 0;
                    $swapFreeBytes = isset($swapFree[1]) ? (float) $swapFree[1] * 1024 : 0;
                    $swapUsedBytes = $swapTotalBytes - $swapFreeBytes;

                    return [
                        'total' => MonitoringFormatter::bytes($totalBytes),
                        'free' => MonitoringFormatter::bytes($availableBytes),
                        'used' => MonitoringFormatter::bytes($usedBytes),
                        'percentage' => (int) round(($usedBytes / $totalBytes) * 100),
                        'swap' => [
                            'total' => MonitoringFormatter::bytes($swapTotalBytes),
                            'used' => MonitoringFormatter::bytes($swapUsedBytes),
                            'percentage' => $swapTotalBytes > 0 ? (int) round(($swapUsedBytes / $swapTotalBytes) * 100) : 0,
                        ],
                    ];
                }
            }
        }

        return [
            'total' => 'Unknown',
            'free' => 'Unknown',
            'used' => 'Unknown',
            'percentage' => 0,
        ];
    }

    // ========================
    // Disk Usage
    // ========================
    public function getDiskUsage()
    {
        return $this->getFormattedDiskUsage()['percentage'];
    }

    public function getFormattedDiskUsage()
    {
        $path = PHP_OS_FAMILY === 'Windows' ? 'C:' : '/';
        $total = @disk_total_space($path);
        $free = @disk_free_space($path);

        if (! $total) {
            return [
                'total' => 'Unknown',
                'free' => 'Unknown',
                'used' => 'Unknown',
                'percentage' => 0,
            ];
        }

        $used = $total - $free;

        return [
            'total' => MonitoringFormatter::bytes($total),
            'free' => MonitoringFormatter::bytes($free),
            'used' => MonitoringFormatter::bytes($used),
            'percentage' => (int) round(($used / $total) * 100),
        ];
    }

    // ========================
    // Active Users (Laravel Session)
    // ========================
    public function getActiveUsers()
    {
        return Cache::remember('monitoring_active_users', 60, function () {
            $driver = config('session.driver');
            $data = ['total' => 0, 'authenticated' => 0];

            try {
                if ($driver === 'database') {
                    if (! DB::getSchemaBuilder()->hasTable('sessions')) {
                        return $data;
                    }
                    $data['total'] = DB::table('sessions')->count();
                    $data['authenticated'] = DB::table('sessions')->whereNotNull('user_id')->count();

                    return $data;
                }

                if ($driver === 'redis') {
                    $connection = config('session.connection') ?? 'default';
                    $redis = Redis::connection($connection);

                    $sessionCookie = config('session.cookie', 'laravel_session');
                    $patterns = [
                        $sessionCookie.':*',
                        str_replace('-', '_', $sessionCookie).':*',
                        str_replace('_', '-', $sessionCookie).':*',
                        '*_session:*',
                    ];

                    $count = 0;
                    foreach ($patterns as $pattern) {
                        $cursor = '0';
                        do {
                            $result = $redis->scan($cursor, ['match' => $pattern, 'count' => 100]);
                            $cursor = $result[0];
                            $count += count($result[1]);
                        } while ($cursor !== '0');
                    }

                    $data['total'] = $count;
                    $data['authenticated'] = 'N/A'; // Redis scan doesn't expose user_id easily

                    return $data;
                }

                if ($driver === 'file') {
                    $path = config('session.files');
                    if (! file_exists($path)) {
                        return $data;
                    }
                    $data['total'] = count(glob($path.'/*'));
                    $data['authenticated'] = 'N/A';

                    return $data;
                }
            } catch (\Exception $e) {
                return $data;
            }

            return $data;
        });
    }

    public function getRegisteredUsers()
    {
        try {
            return User::count();
        } catch (\Exception $e) {
            return 0;
        }
    }

    // ========================
    // System Info
    // ========================
    public function getPhpVersion()
    {
        return PHP_VERSION;
    }

    public function getDatabaseVersion()
    {
        try {
            return DB::connection()->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
        } catch (\Exception $e) {
            return 'Unknown';
        }
    }

    public function getServerIp()
    {
        return request()->server('SERVER_ADDR') ?? gethostbyname(gethostname());
    }

    public function getUptime()
    {
        if (PHP_OS_FAMILY === 'Windows') {
            $output = shell_exec('wmic os get lastbootuptime /value');
            if ($output && preg_match('/LastBootUpTime=(\d+)/', $output, $matches)) {
                $bootTime = $matches[1]; // Format: YYYYMMDDHHMMSS.MMMMMM+UUU
                $year = substr($bootTime, 0, 4);
                $month = substr($bootTime, 4, 2);
                $day = substr($bootTime, 6, 2);
                $hour = substr($bootTime, 8, 2);
                $minute = substr($bootTime, 10, 2);
                $second = substr($bootTime, 12, 2);

                $bootTimestamp = strtotime("$year-$month-$day $hour:$minute:$second");
                $diff = time() - $bootTimestamp;

                return MonitoringFormatter::duration($diff);
            }
        } else {
            $uptime = @file_get_contents('/proc/uptime');
            if ($uptime) {
                $uptime = explode(' ', $uptime)[0];

                return MonitoringFormatter::duration((int) $uptime);
            }
        }

        return 'Unknown';
    }

    // ========================
    // Queue Stats
    // ========================
    public function getQueueStats()
    {
        try {
            // Use Cache Heartbeat (updated by WorkerHeartbeatJob)
            $lastHeartbeat = Cache::get('queue_worker_heartbeat');
            $workerRunning = $lastHeartbeat && (now()->timestamp - $lastHeartbeat) < 300; // 5 minute threshold

            // Fallback to PS if heartbeat is missing (optional, but keep for robustness)
            if (! $workerRunning && PHP_OS_FAMILY !== 'Windows') {
                $output = @shell_exec('ps aux | grep "queue:work" | grep -v grep');
                $workerRunning = ! empty($output);
            }

            // Throughput Estimation (Activities in last 5 minutes)
            $throughput = 0;
            if (DB::getSchemaBuilder()->hasTable('activity_log')) {
                $throughput = DB::table('activity_log')
                    ->where('created_at', '>=', now()->subMinutes(5))
                    ->count();
            }

            return [
                'pending' => DB::table('jobs')->count(),
                'failed' => DB::table('failed_jobs')->count(),
                'worker_active' => (bool) $workerRunning,
                'throughput' => $throughput,
                'load_factor' => $throughput > 0 ? round($throughput / 5, 1) : 0, // tasks per minute
            ];
        } catch (\Exception $e) {
            return [
                'pending' => 0,
                'failed' => 0,
                'worker_active' => false,
            ];
        }
    }

    // ========================
    // Database Info
    // ========================
    public function getDatabaseInfo()
    {
        return Cache::remember('monitoring_database_info', 300, function () {
            try {
                $driver = DB::getDriverName();
                $tables = 0;
                $size = 0;
                $topTables = [];

                // Performance Check
                $start = microtime(true);
                DB::select('SELECT 1');
                $latency = (int) round((microtime(true) - $start) * 1000);

                if ($driver === 'pgsql') {
                    $tableCount = DB::select("SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public'");
                    $tables = $tableCount[0]->count ?? 0;

                    $dbSize = DB::select('SELECT pg_database_size(current_database()) AS size');
                    $size = $dbSize[0]->size ?? 0;

                    // Technical detail: Top 5 tables by size
                    $topTables = DB::select('
                        SELECT relname AS table,
                               pg_size_pretty(pg_total_relation_size(relid)) AS size_pretty,
                               pg_total_relation_size(relid) AS size_bytes
                        FROM pg_stat_user_tables
                        ORDER BY pg_total_relation_size(relid) DESC
                        LIMIT 5
                    ');
                } else {
                    $tableCount = DB::select('SHOW TABLES');
                    $tables = count($tableCount);
                    $dbSize = DB::select('SELECT SUM(data_length + index_length) AS size FROM information_schema.TABLES WHERE table_schema = DATABASE()');
                    $size = $dbSize[0]->size ?? 0;
                }

                return [
                    'tables' => $tables,
                    'size' => MonitoringFormatter::bytes($size),
                    'size_bytes' => $size,
                    'top_tables' => $topTables,
                    'latency' => $latency.'ms',
                    'status' => $latency < 100 ? 'STABLE' : 'DEGRADED',
                ];
            } catch (\Exception $e) {
                return [
                    'tables' => 0,
                    'size' => 'Unknown',
                    'size_bytes' => 0,
                    'top_tables' => [],
                    'latency' => '0ms',
                    'status' => 'OFFLINE',
                ];
            }
        });
    }

    // ========================
    // Redis Info
    // ========================
    public function getRedisStats()
    {
        try {
            $redis = Redis::connection();

            $start = microtime(true);
            $redis->ping();
            $latency = (int) round((microtime(true) - $start) * 1000);

            $info = $redis->info();

            // Flatten if grouped (Predis often returns sections as sub-arrays)
            $flatInfo = [];
            foreach ($info as $key => $value) {
                if (is_array($value)) {
                    $flatInfo = array_merge($flatInfo, $value);
                } else {
                    $flatInfo[$key] = $value;
                }
            }

            return [
                'status' => 'connected',
                'version' => $flatInfo['redis_version'] ?? 'Unknown',
                'memory_used' => $flatInfo['used_memory_human'] ?? 'Unknown',
                'clients' => $flatInfo['connected_clients'] ?? 0,
                'latency' => $latency.'ms',
                'uptime' => MonitoringFormatter::duration((int) ($flatInfo['uptime_in_seconds'] ?? 0)),
            ];
        } catch (\Exception $e) {
            return [
                'status' => 'disconnected',
                'error' => 'Connection failed',
                'version' => 'Unknown',
                'memory_used' => 'Unknown',
                'clients' => 0,
                'latency' => '0ms',
                'uptime' => 'Unknown',
            ];
        }
    }

    public function getRedisClients()
    {
        try {
            $redis = Redis::connection();
            $clientsRaw = $redis->executeRaw(['CLIENT', 'LIST']);

            if (! is_string($clientsRaw)) {
                return ['clients' => [], 'has_reverb' => false];
            }

            $lines = explode("\n", trim($clientsRaw));
            $clients = [];
            $hasReverb = false;

            foreach ($lines as $line) {
                if (empty($line)) {
                    continue;
                }
                $props = [];
                foreach (explode(' ', $line) as $prop) {
                    if (strpos($prop, '=') !== false) {
                        [$k, $v] = explode('=', $prop, 2);
                        $props[$k] = $v;
                    }
                }

                // Identify client based on name or source
                $addr = $props['addr'] ?? 'Unknown';
                $name = $props['name'] ?: ($props['cmd'] === 'subscribe' ? 'Reverb' : 'Worker');

                if (strpos($addr, '127.0.0.1') !== false) {
                    $type = 'Local Node';
                } elseif ($props['cmd'] === 'subscribe') {
                    $type = 'WebSocket';
                    $hasReverb = true;
                } else {
                    $type = 'Background';
                }

                $clients[] = [
                    'id' => $props['id'] ?? '?',
                    'name' => $name,
                    'type' => $type,
                    'addr' => $addr,
                    'age' => MonitoringFormatter::duration((int) ($props['age'] ?? 0)),
                    'idle' => MonitoringFormatter::duration((int) ($props['idle'] ?? 0)),
                    'db' => $props['db'] ?? '0',
                    'cmd' => $props['cmd'] ?? 'N/A',
                ];
            }

            return [
                'clients' => $clients,
                'has_reverb' => $hasReverb,
            ];
        } catch (\Exception $e) {
            return ['clients' => [], 'has_reverb' => false];
        }
    }

    public function getQueueDetails()
    {
        try {
            return DB::table('jobs')
                ->select('id', 'queue', 'payload', 'available_at')
                ->latest('available_at')
                ->take(10)
                ->get()
                ->map(function ($job) {
                    $payload = json_decode($job->payload);

                    return [
                        'id' => $job->id,
                        'name' => $payload->displayName ?? 'Unknown Job',
                        'queue' => $job->queue,
                        'time' => Carbon::createFromTimestamp($job->available_at)->diffForHumans(),
                    ];
                });
        } catch (\Exception $e) {
            return [];
        }
    }

    // ========================
    // App Health
    // ========================
    public function getAppHealth()
    {
        return [
            'maintenance' => app()->isDownForMaintenance(),
            'storage_link' => file_exists(public_path('storage')),
            'env_safe' => ! config('app.debug'), // In production, debug should be false
            'cache_active' => $this->checkCache(),
            'reverb_active' => $this->checkReverbConnection(),
            'logs_size' => MonitoringFormatter::bytes(file_exists(storage_path('logs/laravel.log')) ? filesize(storage_path('logs/laravel.log')) : 0),
        ];
    }

    private function checkCache()
    {
        try {
            Cache::put('monitoring_check', true, 5);

            return Cache::get('monitoring_check') === true;
        } catch (\Exception $e) {
            return false;
        }
    }

    public function checkReverbConnection()
    {
        $host = config('reverb.servers.reverb.host', '127.0.0.1');
        $port = config('reverb.servers.reverb.port', 8080);

        return Cache::remember('monitoring_reverb_status', 30, function () use ($host, $port) {
            // If localhost fails, try 'reverb' container name as fallback for Docker
            $targets = [$host];
            if ($host === 'localhost' || $host === '127.0.0.1') {
                $targets[] = 'reverb';
            }

            foreach ($targets as $target) {
                try {
                    $connection = @fsockopen($target, $port, $errno, $errstr, 0.2); // Reduced timeout to 0.2s
                    if (is_resource($connection)) {
                        fclose($connection);

                        return true;
                    }
                } catch (\Exception $e) {
                    continue;
                }
            }

            return false;
        });
    }

    // ========================
    // Recent Activity
    // ========================
    public function getRecentActivity()
    {
        if (! DB::getSchemaBuilder()->hasTable('activity_log')) {
            return [];
        }

        return DB::table('activity_log')
            ->leftJoin('users', 'activity_log.causer_id', '=', 'users.id')
            ->select('activity_log.*', 'users.name as causer_name')
            ->latest()
            ->take(5)
            ->get()
            ->map(function ($item) {
                return [
                    'description' => ucfirst(str_replace('_', ' ', $item->description)),
                    'subject' => $item->subject_type ? class_basename($item->subject_type) : 'System',
                    'causer' => $item->causer_name ?? 'System',
                    'time' => Carbon::parse($item->created_at)->diffForHumans(),
                ];
            });
    }

    // ========================
    // Backup Info
    // ========================
    public function getBackupStatus()
    {
        $backupDisk = config('backup.backup.destination.disks.0', 'local');
        $backupName = config('backup.backup.name', 'laravel-backup');

        return Cache::remember('monitoring_backup_status', 1800, function () use ($backupDisk, $backupName) {
            try {
                $files = Storage::disk($backupDisk)->files($backupName);
                if (empty($files)) {
                    return ['last_backup' => 'Never', 'count' => 0];
                }

                $lastFile = end($files);
                $timestamp = Storage::disk($backupDisk)->lastModified($lastFile);

                return [
                    'last_backup' => Carbon::createFromTimestamp($timestamp)->diffForHumans(),
                    'count' => count($files),
                    'latest_file' => basename($lastFile),
                ];
            } catch (\Exception $e) {
                return ['last_backup' => 'Unknown', 'count' => 0];
            }
        });
    }

    // ========================
    // SAP RFC Stats
    // ========================
    public function getSapStatus()
    {
        $possiblePaths = [
            base_path('dev_rfc.trc'),
            public_path('dev_rfc.trc'),
            storage_path('logs/dev_rfc.trc'),
        ];

        $exists = false;
        $size = 0;
        foreach ($possiblePaths as $path) {
            if (file_exists($path)) {
                $exists = true;
                $size = filesize($path);
                break;
            }
        }

        return [
            'active' => $exists,
            'size' => MonitoringFormatter::bytes($size),
            'status' => $exists ? 'TRACKING' : 'IDLE',
        ];
    }

    // ========================
    // Mobile Stats
    // ========================
    public function getMobileStats()
    {
        $logFile = storage_path('logs/mobile.log');
        $count = 0;
        if (file_exists($logFile)) {
            // Optimization: Use shell grep -c to count entries without loading file into memory
            $count = (int) shell_exec("grep -c '^\[[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}' ".escapeshellarg($logFile));
        }

        return [
            'total_logs' => $count,
            'last_activity' => file_exists($logFile) ? Carbon::createFromTimestamp(filemtime($logFile))->diffForHumans() : 'Never',
        ];
    }

    // ========================
    // Bundle All Metrics
    // ========================
    public function getAll()
    {
        return Cache::remember('monitoring_full_bundle', 10, function () {
            $redisData = $this->getRedisClients();

            return [
                'os' => PHP_OS_FAMILY,
                'maintenance' => app()->isDownForMaintenance(),
                'hostname' => gethostname(),
                'ip' => $this->getServerIp(),
                'php' => $this->getPhpVersion(),
                'db' => $this->getDatabaseVersion(),
                'uptime' => $this->getUptime(),
                'cpu' => $this->getCpuUsage(),
                'ram' => $this->getFormattedRamUsage(),
                'disk' => $this->getFormattedDiskUsage(),
                'users' => $this->getActiveUsers(),
                'total_users' => $this->getRegisteredUsers(),
                'queues' => $this->getQueueStats(),
                'db_stats' => $this->getDatabaseInfo(),
                'redis' => $this->getRedisStats(),
                'health' => $this->getAppHealth(),
                'activity' => $this->getRecentActivity(),
                'backup' => $this->getBackupStatus(),
                'sap' => $this->getSapStatus(),
                'mobile' => $this->getMobileStats(),
                'redis_clients' => $redisData['clients'] ?? [],
                'has_reverb' => $redisData['has_reverb'] ?? false,
                'queue_details' => $this->getQueueDetails(),
                'last_update' => now()->format('H:i:s'),
            ];
        });
    }
}
