<?php

/**
 * Драйвер файлового кешу
 *
 * @package Flowaxy\Infrastructure\Cache\Drivers
 * @version 1.0.0
 */

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Cache\Drivers;

use Flowaxy\Infrastructure\Cache\CacheDriverInterface;
use Flowaxy\Support\Helpers\FileHelper;

final class FileCacheDriver implements CacheDriverInterface
{
    private string $cacheDir;
    private const CACHE_FILE_EXTENSION = '.cache';

    public function __construct(string $cacheDir)
    {
        $this->cacheDir = rtrim($cacheDir, '/') . '/';
        $this->ensureCacheDir();
    }

    /**
     * {@inheritDoc}
     */
    public function get(string $key, mixed $default = null): mixed
    {
        $filename = $this->getFilename($key);

        if (!FileHelper::exists($filename) || !FileHelper::isReadable($filename)) {
            return $default;
        }

        $data = FileHelper::get($filename);
        if ($data === false) {
            return $default;
        }

        try {
            $cached = unserialize($data, ['allowed_classes' => false]);

            if (!is_array($cached) || !isset($cached['expires']) || !isset($cached['data'])) {
                @unlink($filename);
                return $default;
            }

            if ($cached['expires'] !== 0 && $cached['expires'] < time()) {
                $this->delete($key);
                return $default;
            }

            return $cached['data'];
        } catch (\Exception $e) {
            @unlink($filename);
            return $default;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function set(string $key, mixed $value, ?int $ttl = null): bool
    {
        $expires = $ttl !== null ? time() + $ttl : 0;

        $cached = [
            'data' => $value,
            'expires' => $expires,
            'created' => time(),
        ];

        try {
            $serialized = serialize($cached);
        } catch (\Exception $e) {
            return false;
        }

        $filename = $this->getFilename($key);
        $result = FileHelper::put($filename, $serialized, true);

        return $result !== false;
    }

    /**
     * {@inheritDoc}
     */
    public function delete(string $key): bool
    {
        $filename = $this->getFilename($key);

        if (FileHelper::exists($filename)) {
            return @unlink($filename);
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function has(string $key): bool
    {
        $filename = $this->getFilename($key);

        if (!FileHelper::exists($filename) || !FileHelper::isReadable($filename)) {
            return false;
        }

        $data = FileHelper::get($filename);
        if ($data === false) {
            return false;
        }

        try {
            $cached = unserialize($data, ['allowed_classes' => false]);

            if (!is_array($cached) || !isset($cached['expires'])) {
                @unlink($filename);
                return false;
            }

            if ($cached['expires'] !== 0 && $cached['expires'] < time()) {
                $this->delete($key);
                return false;
            }

            return true;
        } catch (\Exception $e) {
            @unlink($filename);
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function clear(): bool
    {
        $pattern = $this->cacheDir . '*' . self::CACHE_FILE_EXTENSION;
        $files = glob($pattern);

        if ($files === false) {
            return false;
        }

        $success = true;
        foreach ($files as $file) {
            if (FileHelper::isFile($file)) {
                if (!@unlink($file)) {
                    $success = false;
                }
            }
        }

        return $success;
    }

    /**
     * Отримання імені файлу для ключа
     */
    private function getFilename(string $key): string
    {
        $hash = md5($key);
        return $this->cacheDir . $hash . self::CACHE_FILE_EXTENSION;
    }

    /**
     * Створення директорії кешу
     */
    private function ensureCacheDir(): void
    {
        if (!FileHelper::isDirectory($this->cacheDir)) {
            FileHelper::makeDirectory($this->cacheDir, 0755, true);
        }
    }
}
