<?php

declare(strict_types=1);

namespace ApiModel\Infrastructure\Cache;

use Predis\Client as RedisClient;
use Throwable;

final class RedisCache
{
    private function __construct(private readonly RedisClient $client, private readonly string $prefix) {}

    public static function tryCreate(): ?self
    {
        $url = trim((string) ($_ENV['REDIS_URL'] ?? ''));
        $host = trim((string) ($_ENV['REDIS_HOST'] ?? ''));
        if ($url === '' && $host === '') {
            return null;
        }

        $options = [];
        $timeout = trim((string) ($_ENV['REDIS_TIMEOUT'] ?? ''));
        if ($timeout !== '' && is_numeric($timeout)) {
            $options['read_write_timeout'] = (float) $timeout;
        }

        $client = null;
        if ($url !== '') {
            $client = new RedisClient($url, $options);
        } else {
            $db = (int) ($_ENV['REDIS_DATABASE'] ?? $_ENV['REDIS_DB'] ?? '1');
            $pass = trim((string) ($_ENV['REDIS_PASSWORD'] ?? ''));
            $params = [
                'scheme' => 'tcp',
                'host' => $host,
                'port' => (int) ($_ENV['REDIS_PORT'] ?? 6379),
                'database' => $db,
            ];
            if ($pass !== '') {
                $params['password'] = $pass;
            }
            $client = new RedisClient($params, $options);
        }

        $prefix = trim((string) ($_ENV['REDIS_KEY_PREFIX'] ?? 'app:'));
        $prefix = rtrim($prefix, ':') . ':cache:';

        return new self($client, $prefix);
    }

    private function key(string $key): string
    {
        return $this->prefix . $key;
    }

    /** @return array<string,mixed>|null */
    public function getJson(string $key): ?array
    {
        try {
            $raw = $this->client->get($this->key($key));
            if (! is_string($raw) || $raw === '') {
                return null;
            }
            $decoded = json_decode($raw, true);
            return is_array($decoded) ? $decoded : null;
        } catch (Throwable) {
            return null;
        }
    }

    /** @param array<string,mixed> $value */
    public function setJson(string $key, array $value, int $ttlSeconds): void
    {
        try {
            $payload = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
            $this->client->setex($this->key($key), max(1, $ttlSeconds), $payload);
        } catch (Throwable) {
            // cache best-effort
        }
    }

    /** @param array<int,string> $keys */
    public function deleteMany(array $keys): void
    {
        if ($keys === []) {
            return;
        }
        try {
            $full = array_map(fn (string $k): string => $this->key($k), $keys);
            $this->client->del($full);
        } catch (Throwable) {
            // cache best-effort
        }
    }
}

