<?php


namespace Mainto\RpcServer\RpcServer;


use Exception;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use InvalidArgumentException;
use Mainto\RpcServer\Exceptions\BaseNotReportServiceException;
use Mainto\RpcServer\Log\LogHandler;
use Mainto\RpcServer\Protocol\Common\Types\RequestHeaderType;
use Mainto\RpcServer\Protocol\Request\Request;
use Mainto\RpcServer\Protocol\Request\RequestReader;
use Mainto\RpcServer\Protocol\Response\Extend\ResponseExtendError;
use Mainto\RpcServer\Protocol\Response\Response;
use Mainto\RpcServer\RpcUtil\Tool\RpcLog;
use Mainto\RpcServer\Util\IO\Buffer;
use Mainto\RpcServer\Util\Net\Conn;
use Throwable;

class BaseRpcConnection {
    protected Conn $conn;
    protected Response $cacheErrResponse;
    protected ResponseExtendError $cacheExtendError;
    /**
     * @var ConnectionHandler
     */
    private ConnectionHandler $handler;
    private string $streamId;
    private int $pid;
    private bool $isStopped = false;

    private bool $dumpEnable = false;
    /**
     * @var Buffer
     */
    private Buffer $bufIO;

    public function __construct (Conn $conn, string $streamId, ConnectionHandler $handler) {
        $this->handler = $handler;
        $this->streamId = $streamId;
        $this->conn = $conn;
        $this->bufIO = new Buffer($conn);

        $this->pid = getmypid();

        $this->cacheExtendError = new ResponseExtendError();
        $this->cacheErrResponse = new Response();
        $this->cacheErrResponse->useReturnErrType();
        if (config('logging.channels')[config('logging.default')]['handler'] == LogHandler::class) {
            if (config('logging.channels')[config('logging.default')]['with']['level'] == "debug") {
                $this->dumpEnable = true;
            }
        }
    }

    public function start () {
        $this->handler->beforeStart($this->conn);
        while (!$this->isStopped) {
            if ($this->readAndHandle() === false) {
                break;
            }
        }

        $this->close();
    }

    public function readAndHandle (): bool {
        try {
            $request = RequestReader::ReadRequestFrom($this->bufIO)->toRequest();
        } catch (Throwable $exception) {
            Log::error($exception, ["streamId" => $this->streamId, 'pid' => $this->pid]);
            return false;
        }

        $this->dumpRequest($request);
        $startTime = microtime(true);

        switch ($request->getType()) {
            case RequestHeaderType::SidecarStopType:
                Log::debug("exit by recv SidecarStopType signal", ['streamId' => $this->streamId, 'pid' => $this->pid]);
                $response = new Response();
                $response->useReturnOKType();
                $this->isStopped = true;
                break;
            default:
                $response = $this->_handleRequest($request);
        }

        $response->setRuntime(intval((microtime(true) - $startTime) * 1000000));
        $this->dumpResponse($request->getTraceId(), $response);
        $response->writeTo($this->bufIO);
        $this->bufIO->flush();
        $response->resetBodyAndExt();

        if ($this->handler->onSent($this->conn) === false) {
            return false;
        }

        return true;
    }

    protected function dumpRequest (Request $request) {
        if ($this->dumpEnable) {
            Log::debug("server recv req", ['pid' => $this->pid, 'traceId' => $request->getTraceId(), 'data' => $request->toArray()]);
        }
    }

    /**
     * 处理请求 && 友好的处理异常
     *
     * @param Request $request
     * @return Response
     */
    private function _handleRequest (Request $request): Response {
        try {
            return $this->handler->onRequest($request);
        } catch (Throwable $exception) {
            /** @var ResponseExtendError $errExt */
            $this->cacheErrResponse->setExtend($this->cacheExtendError);
            $this->cacheExtendError->setTraceId(isset($request) ? $request->getTraceId() : "");
            $this->cacheExtendError->setErrMsg($exception->getMessage());

            if ($exception instanceof ValidationException) {
                $this->cacheExtendError->setErrorCode(422);
                $this->cacheExtendError->setErrMsg(json_encode($exception->errors()));
            } elseif ($exception instanceof InvalidArgumentException) {
                $this->cacheExtendError->setErrorCode(500);
            } else {
                if (!($exception instanceof BaseNotReportServiceException)) {
                    try {
                        RpcLog::getInstance()->logRequestThrowable($request, $exception, "{$request->getCallClassName()}::{$request->getCallMethodName()}");
                    } catch (Throwable $e) {
                        Log::error($e, ["streamId" => $this->streamId]);
                        // nop
                    }
                }

                $this->cacheExtendError->setErrorCode(intval($exception->getCode()) ?: 500);
            }

            if ($exception instanceof Exception) {
                app(ExceptionHandler::class)->report($exception);
            } else {
                Log::error($exception, ["streamId" => $this->streamId]);
            }

            return $this->cacheErrResponse;
        } finally {
            $this->resourceRecover();
        }
    }

    /**
     * 回收可能导致有问题的资源
     */
    private function resourceRecover () {
        while (DB::transactionLevel()) {
            DB::rollBack();
        }
    }

    protected function dumpResponse (string $traceId, Response $response) {
        if ($this->dumpEnable) {
            Log::debug("server send req", ['pid' => $this->pid, 'traceId' => $traceId, 'data' => $response->toArray()]);
        }
    }

    public function close () {
        $this->handler->onClose($this->conn);

        $this->conn->close();
    }
}
