<?php

namespace Application\Service\Extension\Cache;

use Application\Service\Extension\BaseExtension;
use Application\Service\Extension\Helper\EncryptHelper;
use Application\Service\Extension\Laminas;

/**
 * @method dbPrimarySet(string $tableName, int $id, int $duration = 0)
 */
class Cache extends BaseExtension
{
    private int $_defaultDuration = 0;

    private string $keyPrefix = 'CACHE:';

    private ?DependencyInterface $dependency = null;

    private array $_callMapping = [
        'dbPrimarySet' => [Mapping::class, 'dbPrimarySet']
    ];

    public static function app(): Cache
    {
        return parent::app(); // TODO: Change the autogenerated stub
    }

    public function getAdapter()
    {
        return Laminas::$serviceManager->redisExtend->getRedisInstance();
    }

    /**
     * 设置缓存值
     *
     * 该方法用于向缓存中设置键值对数据，并可以指定缓存的持续时间
     * 如果设置了依赖项，则会为依赖项生成相应的依赖数据
     *
     * @param mixed $key 缓存的键，用于唯一标识缓存项
     * @param mixed $value 缓存的值，可以是任何可以被序列化的数据类型
     * @param int|null $duration 缓存持续时间，以秒为单位，默认为3天如果为null，则不设置过期时间
     *
     * @return bool 返回缓存设置操作的结果，true表示成功，false表示失败
     * @throws \Exception
     */
    public function set($key, $value, ?int $duration = 60 * 60 * 24 * 3): bool
    {
        // 构建完整的缓存键
        $key = $this->buildKey($key);

        // 如果存在依赖项，则生成依赖数据
        if ($this->dependency) {
            // 如果设置了依赖项，请尽量设置过期时间。否则一个缓存依赖多个标签的时候, 可能导致冗余的数据无法被清理。
            $this->dependency->generateDependencyData($key, $duration);
            $this->dependency = null;
        }

        // 调用适配器的set方法，将值序列化后存储，并返回存储结果
        return $this->getAdapter()->set($key, serialize($value), $duration);
    }

    public function get($key)
    {
        $key = $this->buildKey($key);

        return unserialize($this->getAdapter()->get($key));
    }

    public function exist($key): bool
    {
        $key = $this->buildKey($key);

        return (bool)$this->getAdapter()->exists($key);
    }

    public function getOrSet($key, $value = null, array $arguments = [], ?int $duration = 60 * 60 * 24 * 3)
    {
        if ($this->exist($key) === false) {
            if ($value instanceof \Closure) {
                $value = $value(...$arguments);
            }
            $this->set($key, $value, $duration);
        } else {
            $value = $this->get($key);
        }

        return $value;
    }

    public function delete($key)
    {
        if (is_string($key)) {
            return $this->getAdapter()->del($key);
        } elseif (is_array($key)) {
            return $this->getAdapter()->del(...$key);
        }
    }

    /**
     * 构建缓存键
     *
     * 本函数旨在生成符合特定格式的缓存键名，以确保键名的唯一性和合法性
     * 它通过接受一个键值，对其进行处理，最终返回一个带有前缀的键名
     *
     * @param mixed $key 键值，可以是字符串、数组或其他类型
     * @return string 返回带有前缀的缓存键名
     * @throws \Exception
     */
    private function buildKey($key)
    {
//        // 检查键是否为字符串类型
//        if (is_string($key)) {
//            // 如果键是由字母和数字组成且长度不超过32，则直接使用；否则使用md5哈希值
//            $key = ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key);
//        } else {
//            // 对非字符串类型的键进行序列化，以适应复杂类型的键值
//            $serializedKey = serialize($key);
//
//            // 使用序列化后的键值生成md5哈希值，确保键名的唯一性
//            $key = EncryptHelper::md5($serializedKey); // md5($serializedKey);
//        }



        // 在键名前添加前缀，以避免不同缓存间的键名冲突
        return $this->keyPrefix . EncryptHelper::md5($key);
    }

    public function dependency(DependencyInterface $dependency)
    {
        $this->dependency = $dependency;
        return $this;
    }

    public function tag($tagName)
    {
        if (is_string($tagName)) {
            $tagName = (array)$tagName;
        }
        $this->dependency = new TagDependency([
            'tags' => $tagName
        ]);
        return $this;
    }

    public function flush()
    {

    }

    public function __call($name, $arguments)
    {
        if (isset($this->_callMapping[$name])) {
            return $this->getOrSet(__FUNCTION__ . $name, fn() => call_user_func_array($this->_callMapping[$name], $arguments), [],is_int(end($arguments)) ? end($arguments) : 0);
        }

        throw new \Exception("Not support Method: `$name` ");
    }
}