<?php

declare(strict_types=1);

namespace App\Framework\Redis\Services;

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Redis\RedisConnectionPool;
use App\Framework\Redis\ValueObjects\RedisCachePattern;
use App\Framework\Redis\ValueObjects\RedisDatabase;
use App\Framework\Redis\ValueObjects\RedisKeyInfo;
use App\Framework\Redis\ValueObjects\RedisSlowLogEntry;
use Redis;

final readonly class RedisMonitoringService
{
    public function __construct(
        private RedisConnectionPool $connectionPool
    ) {
    }

    /**
     * @return array<string, mixed>
     */
    public function getBasicInfo(): array
    {
        $redis = $this->getRedisClient();
        $info = $redis->info();

        return [
            'status' => 'Connected',
            'version' => $info['redis_version'] ?? 'Unknown',
            'uptime' => $this->formatUptime((int)($info['uptime_in_seconds'] ?? 0)),
            'memory' => Byte::fromBytes((int)($info['used_memory'] ?? 0)),
            'peak_memory' => Byte::fromBytes((int)($info['used_memory_peak'] ?? 0)),
            'clients' => $info['connected_clients'] ?? 0,
            'keys' => $redis->dbsize(),
            'commands_per_sec' => $info['instantaneous_ops_per_sec'] ?? 0,
            'hit_rate' => $this->calculateHitRate($info),
            'evicted_keys' => $info['evicted_keys'] ?? 0,
            'expired_keys' => $info['expired_keys'] ?? 0,
        ];
    }

    /**
     * @return RedisDatabase[]
     */
    public function getDatabases(): array
    {
        $redis = $this->getRedisClient();
        $databases = [];

        for ($db = 0; $db <= 15; $db++) {
            try {
                $redis->select($db);
                $size = $redis->dbsize();

                if ($size > 0) {
                    $databases[] = RedisDatabase::create($db, $size);
                }
            } catch (\Throwable $e) {
                break;
            }
        }

        // Reset to default database
        $redis->select(0);

        return $databases;
    }

    /**
     * @return RedisCachePattern[]
     */
    public function analyzeCachePatterns(): array
    {
        $redis = $this->getRedisClient();

        // Switch to cache database
        $redis->select(1);

        $patterns = [
            'page' => ['pattern' => 'cache:page:*', 'description' => 'Cached page content'],
            'middleware' => ['pattern' => 'cache:middleware_metrics:*', 'description' => 'Middleware performance metrics'],
            'discovery' => ['pattern' => 'cache:discovery:*', 'description' => 'Framework discovery cache'],
            'http_parser' => ['pattern' => 'cache:http_parser:*', 'description' => 'HTTP request parser cache'],
            'template' => ['pattern' => 'cache:template:*', 'description' => 'Template compilation cache'],
            'session' => ['pattern' => 'cache:session:*', 'description' => 'User session data'],
        ];

        $analysis = [];

        foreach ($patterns as $type => $config) {
            $keys = $redis->keys($config['pattern']);
            $totalSize = 0;
            $count = count($keys);

            // Sample keys to estimate size
            $sampleKeys = array_slice($keys, 0, min(10, $count));
            foreach ($sampleKeys as $key) {
                try {
                    $totalSize += $redis->strlen($key);
                } catch (\Throwable $e) {
                    // Key might have expired
                }
            }

            $avgSize = $count > 0 && count($sampleKeys) > 0 ? (int)($totalSize / count($sampleKeys)) : 0;
            $estimatedTotal = $avgSize * $count;

            $analysis[] = RedisCachePattern::create(
                name: $type,
                pattern: $config['pattern'],
                keyCount: $count,
                avgSizeBytes: $avgSize,
                totalSizeBytes: $estimatedTotal,
                description: $config['description']
            );
        }

        // Reset to default database
        $redis->select(0);

        return $analysis;
    }

    /**
     * @return RedisKeyInfo[]
     */
    public function getKeyDetails(int $limit = 20): array
    {
        $redis = $this->getRedisClient();
        $keys = [];

        $allKeys = $redis->keys('*');
        $sampleKeys = array_slice($allKeys, 0, $limit);

        foreach ($sampleKeys as $key) {
            try {
                $ttl = $redis->ttl($key);
                $type = $redis->type($key);
                $size = $redis->strlen($key);
                $preview = $this->getKeyPreview($redis, $key, $type);

                $keys[] = RedisKeyInfo::create(
                    key: $key,
                    redisType: $type,
                    sizeBytes: $size,
                    ttlSeconds: $ttl,
                    preview: $preview
                );
            } catch (\Throwable $e) {
                // Key might have expired
            }
        }

        return $keys;
    }

    /**
     * @return RedisSlowLogEntry[]
     */
    public function getSlowLog(int $limit = 10): array
    {
        $redis = $this->getRedisClient();
        $entries = [];

        try {
            $slowLog = $redis->slowlog('get', $limit);

            foreach ($slowLog as $entry) {
                $entries[] = RedisSlowLogEntry::create(
                    id: $entry[0],
                    timestamp: $entry[1],
                    durationMicros: $entry[2],
                    command: $entry[3]
                );
            }
        } catch (\Throwable $e) {
            // Slow log might not be available
        }

        return $entries;
    }

    /**
     * @return array<string, mixed>
     */
    public function getConnectionPoolInfo(): array
    {
        return [
            'pool_status' => 'Connected',
            'health_check' => 'OK',
            'connection_type' => 'Pool',
        ];
    }

    private function getRedisClient(): Redis
    {
        return $this->connectionPool->getConnection()->getClient();
    }

    private function calculateHitRate(array $info): Percentage
    {
        $hits = $info['keyspace_hits'] ?? 0;
        $misses = $info['keyspace_misses'] ?? 0;
        $total = $hits + $misses;

        if ($total === 0) {
            return Percentage::zero();
        }

        return Percentage::fromRatio($hits, $total);
    }

    private function formatUptime(int $seconds): string
    {
        $duration = Duration::fromSeconds($seconds);

        return $duration->toHumanReadable();
    }

    private function getKeyPreview(Redis $redis, string $key, int $type): string
    {
        try {
            switch ($type) {
                case Redis::REDIS_STRING:
                    $value = $redis->get($key);
                    if (is_string($value)) {
                        return substr($value, 0, 100) . (strlen($value) > 100 ? '...' : '');
                    }

                    return 'Binary data';

                case Redis::REDIS_LIST:
                    $count = $redis->llen($key);

                    return "List with $count items";

                case Redis::REDIS_SET:
                    $count = $redis->scard($key);

                    return "Set with $count members";

                case Redis::REDIS_ZSET:
                    $count = $redis->zcard($key);

                    return "Sorted Set with $count members";

                case Redis::REDIS_HASH:
                    $count = $redis->hlen($key);

                    return "Hash with $count fields";

                default:
                    return 'Unknown type';
            }
        } catch (\Throwable $e) {
            return 'Error reading value';
        }
    }
}
