<?php

declare(strict_types=1);

namespace app\common\library;

use InvalidArgumentException;
use app\common\library\token\TokenExpirationException;

/**
 * Token 管理类
 */
class Token
{
    public array $instance = [];
    public ?object $handler = null;
    protected string $namespace = '\\app\\common\\library\\token\\driver\\';

    public function getDriver(?string $name = null): object
    {
        if ($this->handler !== null) {
            return $this->handler;
        }
        $name = $name ?: $this->getDefaultDriver();
        if ($name === null) {
            throw new InvalidArgumentException(sprintf('Unable to resolve NULL driver for [%s].', static::class));
        }
        return $this->createDriver($name);
    }

    protected function createDriver(string $name): object
    {
        $type = $this->resolveType($name);
        $params = $this->resolveParams($name);
        $class = $this->resolveClass($type);

        if (isset($this->instance[$type])) {
            return $this->instance[$type];
        }
        return new $class(...$params);
    }

    protected function getDefaultDriver(): string
    {
        return $this->getConfig('default');
    }

    protected function getConfig(?string $name = null, mixed $default = null): array|string
    {
        $config = config('buildadmin.token', []);
        if ($name === null) {
            return $config;
        }
        $keys = explode('.', $name);
        $val = $config;
        foreach ($keys as $k) {
            if (!is_array($val) || !array_key_exists($k, $val)) {
                return $default;
            }
            $val = $val[$k];
        }
        return $val;
    }

    protected function resolveParams(string $name): array
    {
        $config = $this->getStoreConfig($name);
        return [$config];
    }

    protected function resolveClass(string $type): string
    {
        $class = str_contains($type, '\\') ? $type : $this->namespace . $this->studly($type);
        if (class_exists($class)) {
            return $class;
        }
        throw new InvalidArgumentException("Driver [{$type}] not supported.");
    }

    protected function getStoreConfig(string $store, ?string $name = null, mixed $default = null): array|string
    {
        $config = $this->getConfig("stores.{$store}");
        if ($config === null) {
            throw new InvalidArgumentException("Store [{$store}] not found.");
        }
        if ($name === null) {
            return $config;
        }
        $keys = explode('.', $name);
        $val = $config;
        foreach ($keys as $k) {
            if (!is_array($val) || !array_key_exists($k, $val)) {
                return $default;
            }
            $val = $val[$k];
        }
        return $val;
    }

    protected function resolveType(string $name): string
    {
        return $this->getStoreConfig($name, 'type', 'Mysql');
    }

    private function studly(string $value): string
    {
        $value = ucwords(str_replace(['-', '_'], ' ', $value));
        return str_replace(' ', '', $value);
    }

    public function set(string $token, string $type, int $userId, ?int $expire = null): bool
    {
        return $this->getDriver()->set($token, $type, $userId, $expire);
    }

    public function get(string $token, bool $expirationException = true): array
    {
        return $this->getDriver()->get($token);
    }

    public function check(string $token, string $type, int $userId, bool $expirationException = true): bool
    {
        return $this->getDriver()->check($token, $type, $userId);
    }

    public function delete(string $token): bool
    {
        return $this->getDriver()->delete($token);
    }

    public function clear(string $type, int $userId): bool
    {
        return $this->getDriver()->clear($type, $userId);
    }

    /**
     * Token 过期检查
     * @throws TokenExpirationException
     */
    public function tokenExpirationCheck(array $token): void
    {
        if (isset($token['expire_time']) && $token['expire_time'] <= time()) {
            throw new TokenExpirationException();
        }
    }
}
