<?php
namespace Vichan\Data\Driver;

defined('TINYBOARD') or exit;

class RedisCacheDriver implements CacheDriver {
	private string $prefix;
	private \Redis $inner;

	public function __construct(string $prefix, string $host, ?int $port, ?string $password, int $database) {

		$this->inner = new \Redis();

		$connected = false;
		try {

			if (str_starts_with($host, 'unix:') || str_starts_with($host, ':')) {
				$ret = \explode(':', $host);
				if (count($ret) < 2) {
					throw new \RuntimeException("Invalid unix socket path $host");
				}

				$connected = $this->inner->pconnect($ret[1]);
			} elseif ($port === null) {
				$connected = $this->inner->pconnect($host);
			} else {

				$connected = $this->inner->pconnect($host, $port, 1.0);
			}
		} catch (\RedisException $e) {

			throw new \RuntimeException('Unable to connect to Redis: ' . $e->getMessage());
		}

		if (!$connected) {

			throw new \RuntimeException('Unable to establish persistent connection to Redis.');
		}

		if ($password) {
			if (!$this->inner->auth($password)) {

				throw new \RuntimeException('Redis authentication failed!');
			}
		}

		$serializer = \Redis::SERIALIZER_JSON; 

		if (!$this->inner->setOption(\Redis::OPT_SERIALIZER, $serializer)) {

			throw new \RuntimeException('Unable to configure Redis serializer');
		}

		if (!$this->inner->select($database)) {

			throw new \RuntimeException('Unable to select Redis database!');
		}

		$this->prefix = $prefix;
	}

	public function get(string $key): mixed {
		try {
			$ret = $this->inner->get($this->prefix . $key);

			if ($ret === false) {

				return null; 
			}

			return $ret;
		} catch (\RedisException $e) {

			return null; 
		}
	}

	public function set(string $key, mixed $value, mixed $expires = false): void {

		$redisKey = $this->prefix . $key;
		try {
			if ($expires === false || $expires <= 0) {
				$success = $this->inner->set($redisKey, $value);
			} else {
				$success = $this->inner->setEx($redisKey, (int)$expires, $value);
			}

		} catch (\RedisException $e) {

		}
	}

	public function delete(string $key): void {
		$redisKey = $this->prefix . $key;
		try {
			$this->inner->del($redisKey); 

		} catch (\RedisException $e) {

		}
	}

	public function flush(): void {

		if (empty($this->prefix)) {

			return; 
		}

		try {

			$iterator = null; 
			$pattern = $this->prefix . '*';
			$keysToDelete = [];

			$this->inner->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY);
			while ($keys = $this->inner->scan($iterator, $pattern, 1000)) {
				if (!empty($keys)) {

					$deletedCount = $this->inner->unlink(...$keys);

				}
			}
		} catch (\RedisException $e) {

		}
	}
}