<?php
declare(strict_types=1);

namespace app\manager\sys;

use think\facade\Config;
use RedisException;
use Redis;
use RedisCluster;

class RedisManager
{
    private static ?self $instance = null;
    private Redis|RedisCluster|null $redis = null;
    private string $prefix;
    
    /**
     * 获取RedisManager实例
     */
    public static function getInstance(?array $config = null): self
    {
        if (self::$instance === null) {
            self::$instance = new self($config);
        }
        return self::$instance;
    }

    /**
     * 构造函数
     */
    private function __construct(?array $config = null)
    {
        $this->initializeConnection($config);
        $this->prefix = Config::get('database.connections.redis.prefix', '');
    }

    /**
     * 初始化Redis连接
     */
    private function initializeConnection(?array $config = null): void
    {
        $config ??= Config::get('database.connections.redis', []);
        $hostName = config("app.host_name");
        
        try {
            match($hostName) {
                'HHKK' => $this->initCluster([
                    '192.168.1.3:7000',
                    '192.168.1.2:7001',
                    '192.168.1.31:7002'
                ]),
                'H36HK' => $this->initCluster([
                    '192.168.1.3:7000',
                    '192.168.1.2:7001',
                    '192.168.1.5:7002',
                    '192.168.1.41:7003',
                    '192.168.1.51:7004'
                ]),
                default => $this->initSingle($config)
            };
        } catch (\Throwable $e) {
            throw new RedisException("Redis连接失败: " . $e->getMessage());
        }
    }

    /**
     * 初始化单机Redis
     */
    private function initSingle(array $config): void
    {
        $this->redis = new Redis();
        $this->redis->connect(
            $config['host'] ?? '127.0.0.1',
            $config['port'] ?? 6379,
            $config['timeout'] ?? 10
        );

        if (!empty($config['password'])) {
            $this->redis->auth($config['password']);
        }

        if (isset($config['db']) && is_numeric($config['db'])) {
            $this->redis->select((int)$config['db']);
        }
    }

    /**
     * 初始化Redis集群
     */
    private function initCluster(array $nodes): void
    {
        $this->redis = new RedisCluster(null, $nodes, 5);
    }

    /**
     * 获取Redis实例
     */
    public function getRedis(): Redis|RedisCluster
    {
        return $this->redis;
    }

    /**
     * String 操作
     */
    public function get(string $key): mixed
    {
        try {
            $value = $this->redis?->get($this->prefix . $key);
            return $this->unserialize($value);
        } catch (\Throwable) {
            return null;
        }
    }

    public function set(string $key, mixed $value, int $expire = 0): bool
    {
        try {
            $value = $this->serialize($value);
            if ($expire > 0) {
                return (bool)$this->redis?->setex($this->prefix . $key, $expire, $value);
            }
            return (bool)$this->redis?->set($this->prefix . $key, $value);
        } catch (\Throwable) {
            return false;
        }
    }

    /**
     * List 操作
     */
    public function lPush(string $key, mixed $value): int
    {
        try {
            return $this->redis?->lPush($this->prefix . $key, $this->serialize($value)) ?? 0;
        } catch (\Throwable) {
            return 0;
        }
    }

    public function lRange(string $key, int $start = 0, int $end = -1): array
    {
        try {
            $data = $this->redis?->lRange($this->prefix . $key, $start, $end) ?? [];
            return array_map(fn($item) => $this->unserialize($item), $data);
        } catch (\Throwable) {
            return [];
        }
    }

    /**
     * Hash 操作
     */
    public function hSet(string $key, string $field, mixed $value): bool
    {
        try {
            return (bool)$this->redis?->hSet(
                $this->prefix . $key,
                $field,
                $this->serialize($value)
            );
        } catch (\Throwable) {
            return false;
        }
    }

    public function hGet(string $key, string|array|null $field = null): mixed
    {
        try {
            if ($field === null) {
                $data = $this->redis?->hGetAll($this->prefix . $key) ?? [];
                return array_map(fn($item) => $this->unserialize($item), $data);
            }
            
            if (is_array($field)) {
                $data = $this->redis?->hMGet($this->prefix . $key, $field) ?? [];
                return array_map(fn($item) => $this->unserialize($item), $data);
            }
            
            $value = $this->redis?->hGet($this->prefix . $key, $field);
            return $this->unserialize($value);
        } catch (\Throwable) {
            return null;
        }
    }

    /**
     * Set 操作
     */
    public function sAdd(string $key, mixed $value): int
    {
        try {
            if (is_array($value)) {
                $count = 0;
                foreach ($value as $v) {
                    $count += $this->redis?->sAdd(
                        $this->prefix . $key,
                        $this->serialize($v)
                    ) ?? 0;
                }
                return $count;
            }
            return $this->redis?->sAdd($this->prefix . $key, $this->serialize($value)) ?? 0;
        } catch (\Throwable) {
            return 0;
        }
    }

    public function sMembers(string $key): array
    {
        try {
            $members = $this->redis?->sMembers($this->prefix . $key) ?? [];
            return array_map(fn($item) => $this->unserialize($item), $members);
        } catch (\Throwable) {
            return [];
        }
    }

    /**
     * 通用操作
     */
    public function delete(string|array $key): bool
    {
        try {
            $keys = is_array($key) 
                ? array_map(fn($k) => $this->prefix . $k, $key)
                : $this->prefix . $key;
            return (bool)$this->redis?->del($keys);
        } catch (\Throwable) {
            return false;
        }
    }

    public function exists(string $key): bool
    {
        try {
            return (bool)$this->redis?->exists($this->prefix . $key);
        } catch (\Throwable) {
            return false;
        }
    }

    /**
     * 序列化处理
     */
    private function serialize(mixed $data): string
    {
        if (is_array($data)) {
            return json_encode($data, JSON_UNESCAPED_UNICODE) ?: '';
        }
        return (string)$data;
    }

    private function unserialize(mixed $data): mixed
    {
        if (!is_string($data)) {
            return $data;
        }
        
        $decoded = json_decode($data, true);
        return json_last_error() === JSON_ERROR_NONE ? $decoded : $data;
    }

    private function __clone() {}
} 