<?php


namespace Mainto\RpcServer\RpcUtil\Setting;


use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Facades\Log;
use Mainto\RpcServer\Protocol\Common\Types\RequestHeaderType;
use Mainto\RpcServer\Protocol\Common\Types\ResponseHeaderType;
use Mainto\RpcServer\Protocol\Request\Extend\RequestExtendHeader;
use Mainto\RpcServer\Protocol\Request\Request;
use Mainto\RpcServer\Protocol\Response\Extend\ResponseExtendError;
use Mainto\RpcServer\RpcClient\RpcClient;
use Mainto\RpcServer\RpcServer\RpcContext;
use ReflectionClass;
use RuntimeException;
use Throwable;

abstract class RpcSetting implements Arrayable {

    private static ?RpcClient $settingClient = null;

    private static ?Request $cacheRequest = null;

    /**
     * RpcSetting constructor.
     */
    protected function __construct () {
        if (!($this instanceof RpcSettingProxy)) {
            throw new RuntimeException("can not construct this instance");
        }

    }

    /**
     * 新建配置实例
     *
     * @param bool $forceRefresh
     * @return static
     */
    public static function newInstance ($forceRefresh = false) {
        self::init();
        static $defaultCache = [];
        static $proxyCache = [];
        $settingName = class_basename(static::class);

        if (!isset($defaultCache[$settingName])) {
            $refClass = new ReflectionClass(static::class);
            $props = $refClass->getProperties();
            $defaultInstance = $refClass->newInstanceWithoutConstructor();
            $defaultSetting = [];

            foreach ($props as $prop) {
                $defaultSetting[$prop->getName()] = $prop->getValue($defaultInstance);
            }

            $defaultCache[$settingName] = $defaultSetting;
        }

        if (self::isUseDefault()) {
            return new RpcSettingProxy(1, $defaultCache[$settingName]);
        }

        // 强制刷新或者不存在旧缓存，oldVersion 设为 ""
        $oldVersion = (isset($proxyCache[$settingName]) && !$forceRefresh) ? ($proxyCache[$settingName])->getVersion() : 0;

        ['Version' => $version, 'Content' => $contentStr] = self::getSetting($settingName, $oldVersion);

        if ($oldVersion == $version) {
            return $proxyCache[$settingName];
        }

        $content = json_decode(base64_decode($contentStr), true);

        $proxy = new RpcSettingProxy($version, array_merge($defaultCache[$settingName], $content));
        $proxyCache[$settingName] = $proxy;
        return $proxy;
    }

    private static function init () {
        if (self::$cacheRequest === null) {
            $requestExtendHeader = new RequestExtendHeader();
            self::$cacheRequest = new Request();
            self::$cacheRequest->setType(RequestHeaderType::SidecarSettingType);
            self::$cacheRequest->setExtend($requestExtendHeader);
        }
    }

    /**
     * 判断是否强制使用默认配置
     *
     * @return bool
     */
    protected static function isUseDefault (): bool {
        static $cache = null;

        if ($cache === null) {
            $cache = (bool)(
                config('app.env') == "testing" ||
                !config('rpc-server.sidecar.enable_setting') ||
                env("FORCE_SETTING_DEFAULT")
            );
        }

        return $cache;
    }

    /**
     * @param string $settingName
     * @return array
     */
    private static function getSetting (string $settingName, string $version): array {
        /** @var RequestExtendHeader $ext */
        self::$cacheRequest->setTraceId(RpcContext::$currentTraceId);
        $ext = self::$cacheRequest->getExt();
        $ext->setHeader("setting_name", [$settingName]);
        $ext->setHeader("version", [$version]);

        try {
            if (self::$settingClient === null) {
                self::$settingClient = RpcClient::getSettingClient();
            }

            $response = self::$settingClient->Do(self::$cacheRequest);
            switch ($response->getType()) {
                case ResponseHeaderType::ReturnOKType:
                    return (array)$response->getBody()->getContent();
                case ResponseHeaderType::ReturnSystemErrType:
                case ResponseHeaderType::ReturnErrType:
                    $extend = $response->getExt();
                    if ($extend instanceof ResponseExtendError) {
                        throw new RuntimeException($extend->getErrMsg(), $extend->getErrorCode());
                    }

                    throw new RuntimeException("unknown response ext");
                default:
                    throw new RuntimeException("unknown response");
            }
        } catch (Throwable $exception) {
            (optional(self::$settingClient))->close();
            self::$settingClient = null;
            Log::error($exception);
            throw new RuntimeException(sprintf("can not find this setting %s::%s", config('rpc-server.service_name'), $settingName));
        }
    }

    /**
     * get config version
     *
     * @return int
     */
    public function getVersion (): int {
        return -1;
    }

    /**
     * Get the instance as an array.
     *
     * @return array
     */
    public function toArray () {
        return [];
    }
}