<?php

namespace Mainto\RpcServer\RpcUtil\Tool\LogDriver;

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 RuntimeException;
use Throwable;

class SidecarSLS extends LogDriveAbstract {
    private static ?RpcClient $slsClient = null;

    private static ?Request $cacheRequest = null;

    public function __construct () {
        parent::__construct();

        if ($this->localMode) {
            throw new RuntimeException('agent sls log driver can not support local mode');
        }

        if (self::$cacheRequest === null) {
            $requestExtendHeader = new RequestExtendHeader();
            self::$cacheRequest = new Request();
            self::$cacheRequest->setType(RequestHeaderType::SidecarSLSType);
            self::$cacheRequest->setExtend($requestExtendHeader);
        }
    }


    public function logThrowable ($request, Throwable $e, string $source = "") {
        if ($source == "") {
            $traceInfo = $this->getTraceInfo(-1);
            $source = $traceInfo['source'];
        }

        $traceStr = "\n";
        foreach (explode("\n", $e->getTraceAsString()) as $key => $value) {
            $traceStr .= $value."\n";
            if (strpos($value, 'php-rpc-framework/src/RpcServer/RpcInvoke.php') !== false
                && strpos($value, 'ReflectionMethod->invokeArgs()') !== false) {
                $traceStr .= "[framework trace]";
                break;
            }
        }

        $this->send($source, "error", [
            "request"       => $this->transObject($request),
            "error_name"    => class_basename($e),
            "error_code"    => $e->getCode(),
            "error_content" => $e->getMessage(),
            "error_file"    => $e->getFile(),
            "error_line"    => $e->getLine(),
            "error_trace"   => $traceStr,
        ]);
    }

    /**
     * log throwable error log
     *
     * @param $request
     * @param $source
     * @param Throwable $e
     */
    public function logRequestThrowable (Request $request, Throwable $e, string $source = "") {
        if ($source == "") {
            $traceInfo = $this->getTraceInfo(-1);
            $source = $traceInfo['source'];
        }

        $traceStr = "\n";
        foreach (explode("\n", $e->getTraceAsString()) as $key => $value) {
            $traceStr .= $value."\n";
            if (strpos($value, 'php-rpc-framework/src/RpcServer/RpcInvoke.php') !== false
                && strpos($value, 'ReflectionMethod->invokeArgs()') !== false) {
                $traceStr .= "[framework trace]";
                break;
            }
        }

        $this->send($source, "error", [
            "header"        => $request->getAllHeaders(),
            "request"       => $request->getAllParams(),
            "error_name"    => class_basename($e),
            "error_code"    => $e->getCode(),
            "error_content" => $e->getMessage(),
            "error_file"    => $e->getFile(),
            "error_line"    => $e->getLine(),
            "error_trace"   => $traceStr,
        ]);
    }

    /**
     * send message to sidecar sls service
     *
     * @param $source
     * @param $logStore
     * @param $contents
     */
    private function send (string $source, string $logStore, array $contents) {
        try {
            /** @var RequestExtendHeader $ext */
            $ext = self::$cacheRequest->getExt();
            $ext->setHeader("channel", [config('rpc-server.sidecar.sls.channel')[$logStore]]);

            $contents['method'] = $source;
            $contents['service_name'] = config('rpc-server.service_name');
            self::$cacheRequest->setBody(Body::newJsonBody(json_encode($contents)));

            if (self::$slsClient === null) {
                self::$slsClient = RpcClient::getSLSClient();
            }

            self::$cacheRequest->setTraceId(RpcContext::$currentTraceId);
            $response = self::$slsClient->Do(self::$cacheRequest);
            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 $e) {
            (optional(self::$slsClient))->close();
            self::$slsClient = null;
            Log::error($e, $contents);
            throw new RuntimeException("can not find this sls server", 0, $e);
        }
    }

    /**
     * log info message
     *
     * @param $title
     * @param $message
     */
    public function info (string $title, $message) {
        $this->log("INFO", $title, $message);
    }

    /**
     * log some message
     *
     * @param string $level
     * @param string $title
     * @param $message
     */
    protected function log (string $level, string $title, $message) {
        $traceInfo = $this->getTraceInfo();
        $this->send($traceInfo['source'], "log", [
            "offset"  => $traceInfo['file'],
            "level"   => $level,
            "title"   => $title,
            "content" => $this->transObject($message),
        ]);
    }

    /**
     * log debug message
     *
     * @param $title
     * @param $message
     */
    public function debug (string $title, $message) {
        $this->log("DEBU", $title, $message);
    }

    /**
     * log notice message
     *
     * @param $title
     * @param $message
     */
    public function notice (string $title, $message) {
        $this->log("NOTI", $title, $message);
    }

    /**
     * log warning message
     *
     * @param $title
     * @param $message
     */
    public function warning (string $title, $message) {
        $this->log("WARN", $title, $message);
    }

    /**
     * log error message
     *
     * @param $source
     * @param $title
     * @param $message
     */
    public function error (string $title, $message) {
        $this->log("ERRO", $title, $message);
    }

    /**
     * log critical message
     *
     * @param $title
     * @param $message
     */
    public function critical (string $title, $message) {
        $this->log("CRIT", $title, $message);
    }
}
