<?php

namespace App\Services;

use Illuminate\Support\Facades\Redis;

/**
 * Service for monitoring Redis server status and performance metrics.
 *
 * Provides a modern visual dashboard displaying Redis statistics including
 * memory usage, client connections, performance metrics, and keyspace data.
 */
class RedisMonitorService
{
    // ANSI color codes
    private const RED = "\033[0;31m";

    private const GREEN = "\033[0;32m";

    private const YELLOW = "\033[1;33m";

    private const BLUE = "\033[0;34m";

    private const MAGENTA = "\033[0;35m";

    private const CYAN = "\033[0;36m";

    private const WHITE = "\033[1;37m";

    private const GRAY = "\033[0;90m";

    private const BOLD = "\033[1m";

    private const DIM = "\033[2m";

    private const NC = "\033[0m"; // No Color

    // Box drawing characters
    private const H_LINE = '─';

    private const V_LINE = '│';

    private const TL_CORNER = '┌';

    private const TR_CORNER = '┐';

    private const BL_CORNER = '└';

    private const BR_CORNER = '┘';

    private const T_RIGHT = '├';

    private const T_LEFT = '┤';

    private const CLEAR_EOL = "\033[K";

    protected string $host;

    protected int $port;

    protected int $refreshInterval;

    protected ?string $connection;

    protected int $termWidth;

    protected int $boxWidth;

    protected bool $running = true;

    /**
     * Create a new RedisMonitorService instance.
     *
     * @param  string|null  $connection  Redis connection name (from config/database.php)
     * @param  int  $refreshInterval  Refresh interval in seconds
     */
    public function __construct(?string $connection = 'default', int $refreshInterval = 30)
    {
        $this->connection = $connection;
        $this->refreshInterval = $refreshInterval;

        // Get connection config
        $config = config("database.redis.{$connection}", config('database.redis.default'));
        $this->host = $config['host'] ?? '127.0.0.1';
        $this->port = (int) ($config['port'] ?? 6379);

        // Get terminal width
        $this->termWidth = $this->getTerminalWidth();
        $this->boxWidth = min($this->termWidth - 2, 100); // Cap at 100 for readability
    }

    /**
     * Get terminal width.
     */
    protected function getTerminalWidth(): int
    {
        $width = 80;
        if (function_exists('exec')) {
            $output = [];
            @exec('tput cols 2>/dev/null', $output);
            if (! empty($output[0]) && is_numeric($output[0])) {
                $width = (int) $output[0];
            }
        }

        return max($width, 60);
    }

    /**
     * Get Redis INFO data.
     */
    public function getRedisInfo(): ?array
    {
        try {
            return Redis::connection($this->connection)->info();
        } catch (\Exception $e) {
            return null;
        }
    }

    /**
     * Start the monitor loop.
     *
     * @param  callable|null  $shouldContinue  Optional callback to control loop continuation
     */
    public function monitor(?callable $shouldContinue = null): void
    {
        // Disable output buffering for immediate display
        while (ob_get_level()) {
            ob_end_flush();
        }

        // Hide cursor
        $this->hideCursor();

        // Setup cleanup handler
        $cleanup = function () {
            $this->showCursor();
            echo self::NC; // Reset colors
        };

        // Register shutdown handler
        register_shutdown_function($cleanup);

        // Handle signals if pcntl is available
        if (function_exists('pcntl_async_signals')) {
            pcntl_async_signals(true);
        }

        if (function_exists('pcntl_signal')) {
            pcntl_signal(SIGINT, function () use ($cleanup) {
                $this->running = false;
                $cleanup();
                exit(0);
            });
            pcntl_signal(SIGTERM, function () use ($cleanup) {
                $this->running = false;
                $cleanup();
                exit(0);
            });
        }

        // Initial clear
        $this->clearScreen();

        while ($this->running) {
            // Check if we should continue
            if ($shouldContinue !== null && ! $shouldContinue()) {
                break;
            }

            // Process signals if available
            if (function_exists('pcntl_signal_dispatch')) {
                pcntl_signal_dispatch();
            }

            // Move cursor to top-left (prevents flicker, better than clearing)
            $this->moveToTop();

            // Get Redis info
            $info = $this->getRedisInfo();

            if ($info === null) {
                $this->displayConnectionError();
            } else {
                $this->displayDashboard($info);
            }

            // Flush output immediately
            if (function_exists('flush')) {
                flush();
            }

            // Sleep in small increments to be responsive to signals
            $sleepTime = $this->refreshInterval;
            while ($sleepTime > 0 && $this->running) {
                sleep(1);
                $sleepTime--;

                // Process signals during sleep
                if (function_exists('pcntl_signal_dispatch')) {
                    pcntl_signal_dispatch();
                }
            }
        }

        $cleanup();
    }

    /**
     * Get a single snapshot of Redis stats without entering monitor mode.
     */
    public function getStats(): array
    {
        $info = $this->getRedisInfo();

        if ($info === null) {
            return [
                'connected' => false,
                'host' => $this->host,
                'port' => $this->port,
            ];
        }

        return $this->parseRedisInfo($info);
    }

    /**
     * Parse Redis INFO into structured data.
     */
    protected function parseRedisInfo(array $info): array
    {
        // Flatten nested arrays if present
        $flatInfo = [];
        foreach ($info as $key => $value) {
            if (is_array($value)) {
                $flatInfo = array_merge($flatInfo, $value);
            } else {
                $flatInfo[$key] = $value;
            }
        }

        // Calculate hit rate
        $hits = (int) ($flatInfo['keyspace_hits'] ?? 0);
        $misses = (int) ($flatInfo['keyspace_misses'] ?? 0);
        $totalOps = $hits + $misses;
        $hitRate = $totalOps > 0 ? round(($hits * 100) / $totalOps, 2) : 0;

        // Parse db keys from all databases
        $dbKeys = 0;
        for ($i = 0; $i <= 15; $i++) {
            $dbKey = "db{$i}";
            if (isset($flatInfo[$dbKey])) {
                $dbValue = $flatInfo[$dbKey];
                if (is_array($dbValue)) {
                    // phpredis returns array with 'keys' key
                    $dbKeys += (int) ($dbValue['keys'] ?? 0);
                } elseif (is_string($dbValue)) {
                    // String format: "keys=123,expires=0,avg_ttl=0"
                    if (preg_match('/keys=(\d+)/', $dbValue, $matches)) {
                        $dbKeys += (int) $matches[1];
                    }
                }
            }
        }

        return [
            'connected' => true,
            'host' => $this->host,
            'port' => $this->port,
            'version' => $flatInfo['redis_version'] ?? 'unknown',
            'uptime_days' => (int) ($flatInfo['uptime_in_days'] ?? 0),
            'uptime_seconds' => (int) ($flatInfo['uptime_in_seconds'] ?? 0),
            'role' => $flatInfo['role'] ?? 'standalone',
            'connected_clients' => (int) ($flatInfo['connected_clients'] ?? 0),
            'blocked_clients' => (int) ($flatInfo['blocked_clients'] ?? 0),
            'total_connections' => (int) ($flatInfo['total_connections_received'] ?? 0),
            'connected_slaves' => (int) ($flatInfo['connected_slaves'] ?? 0),
            'used_memory' => (int) ($flatInfo['used_memory'] ?? 0),
            'used_memory_peak' => (int) ($flatInfo['used_memory_peak'] ?? 0),
            'used_memory_rss' => (int) ($flatInfo['used_memory_rss'] ?? 0),
            'maxmemory' => (int) ($flatInfo['maxmemory'] ?? 0),
            'mem_fragmentation_ratio' => $flatInfo['mem_fragmentation_ratio'] ?? 'N/A',
            'total_commands' => (int) ($flatInfo['total_commands_processed'] ?? 0),
            'ops_per_sec' => (int) ($flatInfo['instantaneous_ops_per_sec'] ?? 0),
            'input_kbps' => (float) ($flatInfo['instantaneous_input_kbps'] ?? 0),
            'output_kbps' => (float) ($flatInfo['instantaneous_output_kbps'] ?? 0),
            'db_keys' => $dbKeys,
            'keyspace_hits' => $hits,
            'keyspace_misses' => $misses,
            'hit_rate' => $hitRate,
            'expired_keys' => (int) ($flatInfo['expired_keys'] ?? 0),
            'evicted_keys' => (int) ($flatInfo['evicted_keys'] ?? 0),
            'rdb_changes' => (int) ($flatInfo['rdb_changes_since_last_save'] ?? 0),
            'rdb_last_save' => (int) ($flatInfo['rdb_last_save_time'] ?? 0),
        ];
    }

    /**
     * Display the Redis monitor dashboard.
     */
    protected function displayDashboard(array $info): void
    {
        $stats = $this->parseRedisInfo($info);
        $currentTime = now()->format('Y-m-d H:i:s');

        // Build the entire output as a single string for atomic write
        $buffer = '';

        // Header
        $buffer .= $this->printHeader("🔴 REDIS MONITOR v{$stats['version']} | {$this->host}:{$this->port}");

        // Server Status
        $buffer .= $this->printSection('Server Status');
        $buffer .= $this->printRow('Status', '● ONLINE', self::GREEN);
        $buffer .= $this->printRow('Role', $stats['role'], self::MAGENTA);
        $buffer .= $this->printRow('Uptime', "{$stats['uptime_days']} days (".$this->formatNumber($stats['uptime_seconds']).' seconds)', self::WHITE);
        $buffer .= $this->printRow('Last Updated', $currentTime, self::GRAY);

        // Memory Usage
        $buffer .= $this->printSection('Memory Usage');
        $buffer .= $this->printRow('Used Memory', $this->formatBytes($stats['used_memory']), self::CYAN);
        $buffer .= $this->printRow('Peak Memory', $this->formatBytes($stats['used_memory_peak']), self::YELLOW);
        $buffer .= $this->printRow('RSS Memory', $this->formatBytes($stats['used_memory_rss']), self::WHITE);
        $buffer .= $this->printRow('Fragmentation Ratio', (string) $stats['mem_fragmentation_ratio'], self::WHITE);

        if ($stats['maxmemory'] > 0) {
            $buffer .= $this->printMeter('Memory Usage', $stats['used_memory'], $stats['maxmemory']);
        }

        // Clients & Connections
        $buffer .= $this->printSection('Clients & Connections');
        $buffer .= $this->printRow('Connected Clients', $this->formatNumber($stats['connected_clients']), self::GREEN);
        $buffer .= $this->printRow('Blocked Clients', $this->formatNumber($stats['blocked_clients']), self::YELLOW);
        $buffer .= $this->printRow('Total Connections', $this->formatNumber($stats['total_connections']), self::WHITE);
        if ($stats['connected_slaves'] > 0) {
            $buffer .= $this->printRow('Connected Slaves', (string) $stats['connected_slaves'], self::MAGENTA);
        }

        // Performance Metrics
        $buffer .= $this->printSection('Performance Metrics');
        $buffer .= $this->printRow('Commands Processed', $this->formatNumber($stats['total_commands']), self::WHITE);
        $buffer .= $this->printRow('Operations/sec', $this->formatNumber($stats['ops_per_sec']).' ops/s', self::GREEN);
        $buffer .= $this->printRow('Network Input', sprintf('%.2f KB/s', $stats['input_kbps']), self::CYAN);
        $buffer .= $this->printRow('Network Output', sprintf('%.2f KB/s', $stats['output_kbps']), self::CYAN);

        // Keyspace Statistics
        $buffer .= $this->printSection('Keyspace Statistics');
        $buffer .= $this->printRow('Total Keys', $this->formatNumber($stats['db_keys']), self::WHITE);
        $buffer .= $this->printRow('Keyspace Hits', $this->formatNumber($stats['keyspace_hits']), self::GREEN);
        $buffer .= $this->printRow('Keyspace Misses', $this->formatNumber($stats['keyspace_misses']), self::RED);
        $buffer .= $this->printRow('Hit Rate', $stats['hit_rate'].'%', self::CYAN);
        $buffer .= $this->printRow('Expired Keys', $this->formatNumber($stats['expired_keys']), self::YELLOW);
        $buffer .= $this->printRow('Evicted Keys', $this->formatNumber($stats['evicted_keys']), self::RED);

        // Persistence
        $buffer .= $this->printSection('Persistence');
        $buffer .= $this->printRow('Changes Since Save', $this->formatNumber($stats['rdb_changes']), self::WHITE);
        if ($stats['rdb_last_save'] > 0) {
            $lastSave = date('Y-m-d H:i:s', $stats['rdb_last_save']);
            $buffer .= $this->printRow('Last Save', $lastSave, self::GRAY);
        }

        $buffer .= $this->printFooter();
        $buffer .= self::DIM." Press Ctrl+C to exit | Refresh: {$this->refreshInterval}s".self::NC.self::CLEAR_EOL."\n";

        // Clear any remaining content below
        $buffer .= "\033[J";

        // Output the entire buffer at once
        echo $buffer;
    }

    /**
     * Display connection error screen.
     */
    protected function displayConnectionError(): void
    {
        $buffer = '';
        $buffer .= $this->printHeader('⚠ REDIS MONITOR - CONNECTION FAILED');
        $buffer .= $this->printRow('Status', '● OFFLINE - Cannot connect to Redis', self::RED);
        $buffer .= $this->printRow('Host', "{$this->host}:{$this->port}", self::YELLOW);
        $buffer .= $this->printRow('Retrying in', "{$this->refreshInterval} seconds...", self::GRAY);
        $buffer .= $this->printFooter();
        $buffer .= self::DIM." Press Ctrl+C to exit | Refresh: {$this->refreshInterval}s".self::NC.self::CLEAR_EOL."\n";
        $buffer .= "\033[J";

        echo $buffer;
    }

    /**
     * Print dashboard header.
     */
    protected function printHeader(string $title): string
    {
        // Strip emoji for length calculation (emoji can be multi-byte)
        $titleLen = mb_strlen(preg_replace('/[\x{1F300}-\x{1F9FF}]/u', '  ', $title));
        $padding = max(0, (int) (($this->boxWidth - $titleLen - 2) / 2));
        $line = str_repeat(self::H_LINE, $this->boxWidth);

        $buffer = self::CYAN.self::TL_CORNER.$line.self::TR_CORNER.self::NC.self::CLEAR_EOL."\n";
        $buffer .= self::CYAN.self::V_LINE.self::NC;
        $buffer .= str_repeat(' ', $padding);
        $buffer .= self::BOLD.self::WHITE.$title.self::NC;
        $buffer .= str_repeat(' ', max(0, $this->boxWidth - $padding - $titleLen));
        $buffer .= self::CYAN.self::V_LINE.self::NC.self::CLEAR_EOL."\n";
        $buffer .= self::CYAN.self::T_RIGHT.$line.self::T_LEFT.self::NC.self::CLEAR_EOL."\n";

        return $buffer;
    }

    /**
     * Print section header.
     */
    protected function printSection(string $title): string
    {
        $remainingWidth = $this->boxWidth - mb_strlen($title) - 5;
        $line = str_repeat(self::H_LINE, max(0, $remainingWidth));

        return self::CYAN.self::T_RIGHT.self::H_LINE.self::H_LINE.self::NC.' '
            .self::YELLOW.self::BOLD.$title.self::NC.' '
            .self::CYAN.$line.self::T_LEFT.self::NC.self::CLEAR_EOL."\n";
    }

    /**
     * Print a data row.
     */
    protected function printRow(string $label, string $value, string $color = self::WHITE): string
    {
        $labelWidth = 25;
        $valueWidth = $this->boxWidth - $labelWidth - 3;

        $label = str_pad($label, $labelWidth);
        $value = str_pad(mb_substr($value, 0, $valueWidth), $valueWidth);

        return self::CYAN.self::V_LINE.self::NC.' '
            .self::DIM.$label.self::NC.' '
            .$color.$value.self::NC
            .self::CYAN.self::V_LINE.self::NC.self::CLEAR_EOL."\n";
    }

    /**
     * Print a progress meter.
     */
    protected function printMeter(string $label, int $current, int $max): string
    {
        $labelWidth = 25;
        $meterWidth = min(30, $this->boxWidth - $labelWidth - 10);

        $percentage = $max > 0 ? (int) (($current * 100) / $max) : 0;
        $filled = (int) (($percentage * $meterWidth) / 100);
        $empty = $meterWidth - $filled;

        // Choose color based on percentage
        $color = self::GREEN;
        if ($percentage > 80) {
            $color = self::RED;
        } elseif ($percentage > 60) {
            $color = self::YELLOW;
        }

        $meter = $color.str_repeat('█', $filled).self::GRAY.str_repeat('░', $empty).self::NC;

        $label = str_pad($label, $labelWidth);
        $percentStr = sprintf('%3d%%', $percentage);
        $remainingSpace = $this->boxWidth - $labelWidth - $meterWidth - strlen($percentStr) - 4;

        return self::CYAN.self::V_LINE.self::NC.' '
            .self::DIM.$label.self::NC.' '
            .$meter.' '.self::WHITE.$percentStr.self::NC
            .str_repeat(' ', max(0, $remainingSpace))
            .self::CYAN.self::V_LINE.self::NC.self::CLEAR_EOL."\n";
    }

    /**
     * Print dashboard footer.
     */
    protected function printFooter(): string
    {
        $line = str_repeat(self::H_LINE, $this->boxWidth);

        return self::CYAN.self::BL_CORNER.$line.self::BR_CORNER.self::NC.self::CLEAR_EOL."\n";
    }

    /**
     * Format bytes into human-readable string.
     */
    protected function formatBytes(int $bytes): string
    {
        if ($bytes >= 1073741824) {
            return sprintf('%.2f GB', $bytes / 1073741824);
        } elseif ($bytes >= 1048576) {
            return sprintf('%.2f MB', $bytes / 1048576);
        } elseif ($bytes >= 1024) {
            return sprintf('%.2f KB', $bytes / 1024);
        }

        return $bytes.' B';
    }

    /**
     * Format number with thousands separators.
     */
    protected function formatNumber(int $number): string
    {
        return number_format($number);
    }

    /**
     * Hide terminal cursor.
     */
    protected function hideCursor(): void
    {
        echo "\033[?25l";
    }

    /**
     * Show terminal cursor.
     */
    protected function showCursor(): void
    {
        echo "\033[?25h";
    }

    /**
     * Move cursor to top-left.
     */
    protected function moveToTop(): void
    {
        echo "\033[H";
    }

    /**
     * Clear the screen.
     */
    protected function clearScreen(): void
    {
        echo "\033[2J\033[H";
    }
}
