<?php

declare(strict_types=1);

namespace BotGuard\Core\Cache;

/**
 * File-based cache driver.
 *
 * Stores each entry as a JSON file containing the value and an expiry
 * timestamp. Uses file locking for the increment() counter so concurrent
 * requests (rate limiting) increment safely. Suitable as a zero-dependency
 * default; swap for RedisCache in production for higher throughput.
 */
final class FileCache implements CacheInterface
{
    public function __construct(private string $directory)
    {
        if (! is_dir($this->directory)) {
            @mkdir($this->directory, 0775, true);
        }
    }

    public function get(string $key, mixed $default = null): mixed
    {
        $data = $this->read($this->path($key));

        return $data === null ? $default : $data['value'];
    }

    public function put(string $key, mixed $value, int $ttlSeconds): void
    {
        $this->write($this->path($key), [
            'value'   => $value,
            'expires' => time() + $ttlSeconds,
        ]);
    }

    public function has(string $key): bool
    {
        return $this->read($this->path($key)) !== null;
    }

    public function forget(string $key): void
    {
        @unlink($this->path($key));
    }

    public function increment(string $key, int $ttlSeconds): int
    {
        $path = $this->path($key);
        $handle = fopen($path, 'c+');

        if ($handle === false) {
            return 1;
        }

        try {
            flock($handle, LOCK_EX);

            $raw = stream_get_contents($handle) ?: '';
            $data = json_decode($raw, true);
            $now = time();

            if (! is_array($data) || ($data['expires'] ?? 0) < $now) {
                $data = ['value' => 0, 'expires' => $now + $ttlSeconds];
            }

            $data['value'] = (int) $data['value'] + 1;

            ftruncate($handle, 0);
            rewind($handle);
            fwrite($handle, json_encode($data));
            fflush($handle);

            return (int) $data['value'];
        } finally {
            flock($handle, LOCK_UN);
            fclose($handle);
        }
    }

    private function path(string $key): string
    {
        return $this->directory . DIRECTORY_SEPARATOR . 'c_' . sha1($key) . '.cache';
    }

    /**
     * @return array{value:mixed, expires:int}|null
     */
    private function read(string $path): ?array
    {
        if (! is_file($path)) {
            return null;
        }

        $data = json_decode((string) file_get_contents($path), true);

        if (! is_array($data) || ($data['expires'] ?? 0) < time()) {
            @unlink($path);

            return null;
        }

        return $data;
    }

    private function write(string $path, array $data): void
    {
        @file_put_contents($path, json_encode($data), LOCK_EX);
    }
}
