<?php


namespace Mainto\RpcServer\RpcServer;


use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Validation\ValidationException;
use Mainto\RpcServer\Exceptions\RpcMethodNotFoundException;
use Mainto\RpcServer\Exceptions\RpcNotSupportException;
use Mainto\RpcServer\Exceptions\RpcRuntimeException;
use Mainto\RpcServer\Protocol\Common\Body;
use Mainto\RpcServer\Protocol\Common\Types\BodyType;
use Mainto\RpcServer\Protocol\Request\Request;
use Mainto\RpcServer\Protocol\Response\Extend\ResponseExtendHeader;
use Mainto\RpcServer\Protocol\Response\Response;

class RpcInvoke {
    /**
     * @var array [RpcFormat => Response]
     */
    public static array $cacheResponse = [];

    public static function initCacheResponse () {
        $jsonResponse = new Response();
        $jsonResponse->useReturnOKType();
        $jsonResponse->setBody(Body::newJsonBody("{}"));

        self::$cacheResponse['Json'] = $jsonResponse;
        self::$cacheResponse['Jsonp'] = $jsonResponse;

        $rawResponse = new Response();
        $rawResponse->useReturnOKType();
        $rawResponse->setBody(Body::newRawBody(""));
        self::$cacheResponse['Raw'] = $rawResponse;
    }

    public static function invoke (Request $request): Response {
        /** @var RpcContext $context */
        /** @var Response $response */
        [$context, $ret, $response] = self::invokeBase($request);

        if ($ret instanceof Response) {
            return $ret;
        }

        // body
        $body = $response->getBody();
        switch ($body->getBodyType()) {
            case BodyType::JSON:
                if ($ret instanceof Jsonable) {
                    $encode = (string) $ret->toJson();
                } else {
                    $encode = json_encode($ret instanceof Arrayable ? $ret->toArray() : $ret);

                    if ($encode === false) {
                        throw new RpcRuntimeException("the response can not convert to json: ".json_last_error_msg());
                    }
                }

                $body->setBodyBytes($encode);
                break;
            case BodyType::RAW:
                $body->setBodyBytes((string)$ret);
                break;
            default:
                throw new RpcNotSupportException("body type not support: ".$body->getBodyType());
        }

        // responseHeaders
        $responseHeaders = $context->getResponseHeader();
        if ($responseHeaders) {
            $ext = new ResponseExtendHeader();
            foreach ($responseHeaders as $key => $value) {
                $ext->setHeader($key, (array)$value);
            }

            $response->setExtend($ext);
        }

        return $response;
    }

    /**
     * @param Request $request
     * @return array
     * @throws ValidationException
     */
    public static function invokeBase (Request $request) {
        $fullClassName = $request->getCallClassName();

        $controller = RpcRouter::getInstance()->getController($fullClassName);
        if (!$controller) {
            throw new RpcMethodNotFoundException(sprintf("%s::%s", $fullClassName, $request->getCallMethodName()));
        }

        $method = RpcRouter::getInstance()->getControllerMethod($fullClassName, $request->getCallMethodName());

        if (!$method) {
            throw new RpcMethodNotFoundException(sprintf("%s::%s", $fullClassName, $request->getCallMethodName()));
        }

        $params = $request->getAllParams();

        // validate
        $method->validate($params);
        $rpcParams = new RpcParams($params);

        $rpcParams->_declareNames = $method->getRequestDeclareNames();

        $context = new RpcContext($request, $rpcParams);
        $baseRet = $method->getRefInstance()->invokeArgs($controller->getInstance(), $method->buildMethodParams($context, $params));

        // session
        $context->saveSession();

        return [$context, $baseRet, self::$cacheResponse[$method->resSerialize]];
    }
}