<?php


namespace Mainto\RpcServer\RpcUtil;

use Exception;
use Illuminate\Support\Facades\Log;
use Mainto\RpcServer\RpcClient\RpcClient;
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\Extend\RequestExtendHeader;
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 Ramsey\Uuid\Uuid;
use RuntimeException;
use Throwable;

class RpcMessageQueue {
    private const WebsocketQueue = "micro-ws-send-queue";
    private const MessageExchange = "micro-msg-main-exchange";
    private const MessageQueue = "micro-msg-main-queue";

    public const PERSISTENT = 'persistent';
    public const TRANSIENT = 'transient';

    private static ?RpcMessageQueue $instance = null;

    private string $serviceName = "";

    private ?RpcClient $messageQueueClient = null;

    public static function getInstance (): RpcMessageQueue {
        if (!self::$instance) {
            self::$instance = new RpcMessageQueue();
            self::$instance->serviceName = config('rpc-server.service_name');
        }

        return self::$instance;
    }

    private function mustGetUuid4 () {
        try {
            return Uuid::uuid4();
        } catch (Exception $e) {
            throw new RuntimeException($e->getCode(), $e->getMessage());
        }
    }

    /**
     * 发送消息到 MessageQueue
     * @param string $recvName 接受消息名
     * @param array $param     发送的消息
     * @param string $mode     持久化模式 [默认持久化]
     */
    public function addMessageToQueue (string $recvName, array $param = [], $mode = self::PERSISTENT) {
        $param['_uuid_'] = $this->mustGetUuid4();

        $request = new Request();
        $ext = new RequestExtendHeader();
        $ext->addHeader("content_type", "text/json");
        $ext->addHeader("key", self::MessageQueue);
        $ext->addHeader("app_id", $this->serviceName);
        $ext->addHeader("type", $recvName);
        $ext->addHeader("delivery_mode", $mode);
        $ext->addHeader("mq_headers", json_encode([
            "trace-id" => RpcContext::$currentTraceId,
        ]));
        //        $ext->addHeader("key", "");

        $request->setTypeWithExtend(RequestHeaderType::MessageNormalType, $ext);
        $request->setBody(Body::newJsonBody(json_encode($param)));

        $this->doRequest($request);
    }

    /**
     * @param Request $request
     */
    private function doRequest (Request $request): void {
        try {
            if ($this->messageQueueClient === null) {
                $this->messageQueueClient = RpcClient::getMessageQueueClient();
            }

            $response = $this->messageQueueClient->Do($request);
            switch ($response->getType()) {
                case ResponseHeaderType::ReturnOKType:
                    break;
                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($this->messageQueueClient))->close();
            $this->messageQueueClient = null;
            Log::error($exception);

            if ($exception instanceof ServerNotFoundException) {
                throw new RuntimeException("can not find message queue", 0, $exception);
            }
            throw new RuntimeException("can not connect to message queue", 0, $exception);
        }
    }

    /**
     * 发送消息到 WebSocket
     * @param string $roomName 接受房间名
     * @param string $clientId 接受客户端ID
     * @param string $content  发送的消息
     * @param string $mode     持久化模式 [默认非持久化]
     */
    public function addMessageToWebSocket (string $roomName, string $clientId, string $content, $mode = self::PERSISTENT) {
        $request = new Request();
        $ext = new RequestExtendHeader();
        $ext->addHeader("content_type", "text/json");
        $ext->addHeader("app_id", $this->serviceName);
        $ext->addHeader("delivery_mode", $mode);
        $ext->addHeader("key", self::WebsocketQueue);
        $ext->addHeader("mq_headers", json_encode([
            "room-name" => $roomName,
            "user-id"   => $clientId,
            "trace-id"  => RpcContext::$currentTraceId,
        ]));

        $request->setTypeWithExtend(RequestHeaderType::MessageNormalType, $ext);
        $request->setBody(Body::newJsonBody($content));

        $this->doRequest($request);
    }

    /**
     * 发送延迟消息
     * @param int $delayTime   延迟时间 [单位: ms]
     * @param string $recvName 接受消息名
     * @param array $param     发送的消息
     * @param string $mode     持久化模式 [默认持久化]
     */
    public function sendDelayMessage (int $delayTime, string $recvName, array $param = [], $mode = self::PERSISTENT) {
        $param['_uuid_'] = $this->mustGetUuid4();

        $request = new Request();
        $ext = new RequestExtendHeader();
        $ext->addHeader("content_type", "text/json");
        $ext->addHeader("key", self::MessageQueue);
        $ext->addHeader("app_id", $this->serviceName);
        $ext->addHeader("type", $recvName);
        $ext->addHeader("delivery_mode", $mode);
        $ext->addHeader("mq_headers", json_encode([
            "delay-time" => $delayTime,
            "trace-id"   => RpcContext::$currentTraceId,
        ]));

        $request->setTypeWithExtend(RequestHeaderType::MessageDelayType, $ext);
        $request->setBody(Body::newJsonBody(json_encode($param)));

        $this->doRequest($request);
    }

    public function __destruct () {
        if ($this->messageQueueClient !== null) {
            $this->messageQueueClient->close();
        }
    }

    public static function getMessageExchange () {
        return self::MessageExchange;
    }

    public static function getMessageQueue () {
        return self::MessageQueue;
    }

    public static function getWebsocketQueue () {
        return self::WebsocketQueue;
    }
}