<?php
/**
 * Created by PhpStorm.
 * User: jiuli
 * Date: 2018/4/4
 * Time: 下午1:29
 */

namespace Mainto\RpcServer\RpcClient;


use Illuminate\Support\Facades\Log;
use Mainto\RpcServer\Exceptions\RpcInvokeException;
use Mainto\RpcServer\Exceptions\RpcRuntimeException;
use Mainto\RpcServer\Protocol\Common\Body;
use Mainto\RpcServer\Protocol\Common\Types\RequestHeaderType;
use Mainto\RpcServer\Protocol\Common\Types\ResponseHeaderType;
use Mainto\RpcServer\Protocol\Request\Request;
use Mainto\RpcServer\Protocol\Response\Extend\ResponseExtendError;
use Mainto\RpcServer\RpcServer\RpcContext;
use Mainto\RpcServer\Util\Net\Exceptions\ServerNotFoundException;
use RuntimeException;
use Throwable;

class RpcClass {
    protected static array $instances = [];

    /**
     * Rpc 客户端
     * @var RpcClient
     */
    protected ?RpcClient $client;
    /**
     * 调用服务名
     * @var string
     */
    protected string $serviceName;
    /**
     * 调用类名
     * @var string
     */
    protected string $className;

    /**
     * RpcClass constructor.
     * @param RpcClient $client
     * @param string $serviceName
     * @param string $className
     */
    protected function __construct (?RpcClient $client, string $serviceName, string $className) {
        $this->client = $client;
        $this->serviceName = $serviceName;
        $this->className = $className;
    }

    /**
     * 获得一个远程调用实例（如果有设置 Mock，获得的是 Mock 实例）
     *
     * @param $serviceName
     * @param $className
     * @return RpcClass
     */
    public static function getClass ($serviceName, $className): RpcClass {
        if (!isset(self::$instances[$serviceName][$className])) {
            self::$instances[$serviceName][$className] = new RpcClass(self::getRemoteClient($serviceName), $serviceName, $className);
        }

        return self::$instances[$serviceName][$className];
    }

    /**
     * 获取远程调用客户端
     *
     * @param $serviceName
     * @return RpcClient
     */
    protected static function getRemoteClient ($serviceName): RpcClient {
        try {
            return RpcClientFactory::getClient("unix", $serviceName);
        } catch (ServerNotFoundException $exception) {
            throw new RuntimeException("can not find server {$serviceName}");
        }
    }

    /**
     * 获取远程调用客户端
     *
     * @param $serviceName
     */
    public static function removeRemoteClient ($serviceName): void {
        RpcClientFactory::clearClient("unix", $serviceName);

        unset(self::$instances[$serviceName]);
    }

    /**
     * 注册一个 Mock 的类
     *
     * @param $serviceName
     * @param $className
     * @return RpcMockClass
     */
    public static function registerMockClass ($serviceName, $className): RpcMockClass {
        if (!isset(self::$instances[$serviceName][$className]) || !(self::$instances[$serviceName][$className] instanceof RpcMockClass)) {
            self::$instances[$serviceName][$className] = new RpcMockClass($serviceName, $className);
        }

        return self::$instances[$serviceName][$className];
    }

    /**
     * 反注册一个 Mock 的类
     *
     * @param $serviceName
     * @param $className
     * @return bool
     */
    public static function unregisterMockClass ($serviceName, $className): bool {
        if (isset(self::$instances[$serviceName][$className]) && self::$instances[$serviceName][$className] instanceof RpcMockClass) {
            unset(self::$instances[$serviceName][$className]);
            return true;
        }

        return false;
    }

    /**
     * 反注册所有的 Mock 类
     *
     * @return bool
     */
    public static function unregisterAllMockClass (): bool {
        foreach (self::$instances as $serviceName => $classes) {
            foreach ($classes as $className => $insClass) {
                if ($insClass instanceof RpcMockClass) {
                    unset(self::$instances[$serviceName][$className]);
                }
            }
        }

        return true;
    }

    /**
     * 函数调用
     * @param $name
     * @param $arguments
     * @return mixed
     * @throws Throwable
     */
    public function __call ($name, $arguments) {
        if (count($arguments) == 1) {
            $arguments = $arguments[0] ?? $arguments;
        }

        $request = new Request();
        $request->setCallClassAndMethod($this->className, $name);
        $request->setType(RequestHeaderType::InvokeNormalType);
        if ($arguments) {
            $request->setBody(Body::newJsonBody(json_encode($arguments)));
        }
        $request->setTraceId(RpcContext::$currentTraceId);

        // todo retry
        try {
            $response = $this->client->Do($request);
            switch ($response->getType()) {
                case ResponseHeaderType::ReturnOKType:
                    if ($response->hasBody()) {
                        return $response->getBody()->getContent();
                    } else {
                        return null;
                    }
                    break;
                case ResponseHeaderType::ReturnSystemErrType:
                case ResponseHeaderType::ReturnErrType:
                    $extend = $response->getExt();
                    if ($extend instanceof ResponseExtendError) {
                        throw new RpcInvokeException($extend->getErrMsg(), $extend->getErrorCode());
                    }

                    throw new RuntimeException("unknown response ext");
                default:
                    throw new RuntimeException("unknown response");
            }
        } catch (Throwable $exception) {
            if ($exception instanceof RpcRuntimeException) {
                throw $exception;
            }
            self::removeRemoteClient($this->serviceName);
            (optional($this->client))->close();
            Log::error($exception);
            throw new RuntimeException("can not connect to server {$this->serviceName}", 0, $exception);
        }
    }
}
