<?php

namespace Application\Command\Swoole\Server;

use Application\Command\BasicCommand;

use Application\Service\Extension\ErrorHandler;
use Application\Service\Extension\Helper\StringHelper;
use Application\Service\Extension\Laminas;
use Swoole\Coroutine;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Process;
use Swoole\WebSocket\CloseFrame;
use Swoole\Coroutine\Http\Server;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use function Swoole\Coroutine\run;

/**
 * https://wiki.swoole.com/#/coroutine/ws_server
 */
class WebSocketCommand extends BasicCommand
{
    /** @var int 监听的端口 */
    const LISTEN_PORT = 9502;

    /**
     * 监听host
     * @var string
     */
    public string $host = '0.0.0.0';

    public array $serverConfig = [
        'daemonize' => true,
        'heartbeat_idle_time' => 60 * 20,  //允许最大空闲时间
        'heartbeat_check_interval' => 60 * 20,  //心跳检测间隔
        'ssl_cert_file' => APP_PATH . '/config/autoload/ssl.crt',
        'ssl_key_file' => APP_PATH . '/config/autoload/ssl.key',
    ];

    /**
     * 用来存储 消息模板。用来做缓存
     * @var array
     */
    protected static array $messageTemplateContext = [];

    protected static array $onlineList = [
        'user' => [], // 用户对应的socket连接
    ];

    public function execute(InputInterface $input, OutputInterface $output)
    {
        set_time_limit(0);// 设置超时时间为无限,防止超时

//        Process::daemon();

        run(function () {
            try {
                $server = new Server($this->host, self::LISTEN_PORT, true);
                $server->set($this->serverConfig);
                $server->handle('/', $this->onHandle());

                echo "ws服务已开启 {$this->host}:" . self::LISTEN_PORT . PHP_EOL;
                $server->start();
            } catch (\Throwable $e) {
                echo "ws开启失败. {$e->getMessage()}" . PHP_EOL;
            }
        });
        return 0;
    }

    /**
     * 处理首次请求。验证token.
     * @param Request $request
     * @param Response $ws
     */
    public function handleRequest(Request $request, Response $ws)
    {
        $header = $request->header;
        $token = $header['token'] ?? '';
        // 设置token
        Laminas::setToken($token);

//        if ($token) {
//            // 用户id => socket连接
//            self::$onlineList['user'][$this->getIdentityId()][] = $ws;
//            \Co\defer(function() {
//                unset(self::$onlineList['user'][$this->getIdentityId()]);
//            });
//        }
    }

    /**
     * 处理事件
     * @return \Closure
     */
    public function onHandle(): \Closure
    {
        return function (Request $request, Response $ws) {
            try {
                // 首次连接
                $this->handleRequest($request, $ws);
                // post请求
                if (isset($request->post)) {
                    $this->handleConnect($request, $ws, null);
                    $ws->close();
                } else {
                    // websocket
                    $ws->upgrade();
                    while (true) {
                        $frame = $ws->recv();
                        if ($frame === '') {
                            $ws->close();
                            break;
                        } else if ($frame === false && !isset($request->post)) {
//                            echo 'errorCode: ' . swoole_last_error() . "\n";
                            $ws->close();
                            break;
                        } else {
                            $this->handleConnect($request, $ws, $frame);
                        }
                    }
                }
            } catch (\Throwable $exception) {
                ErrorHandler::log2txt(print_r([
                    'type' => get_class($exception),
                    'file' => method_exists($exception, 'getFile') ? $exception->getFile() : '',
                    'errorMessage' => $exception->getMessage(),
                    'executeSql' => method_exists($exception, 'getExecuteSql') ? $exception->getExecuteSql() : null,
                    'line' => $exception->getLine(),
                    'stack-trace' => explode("\n", $exception->getTraceAsString()),
                ]), true);
            }
        };
    }

    private function handleConnect($request, $ws, $frame)
    {
        $d = $frame->data ?? $request->post['data'];
        // 心跳
        if ($d === 'ping') {
            $ws->push('pong');
        }

        if (($data = StringHelper::jsonDecode($d, false)) !== false) {
            if ($response = $this->handleMessage($data, $request, $ws)) {
                $ws->push(json_encode($response));
            }
        }

        if ($d == 'close' || $request->post || (is_object($frame) && get_class($frame) === CloseFrame::class)) {
            $ws->close();
        }
    }

    /**
     * ```
     * 小程序端拉取所有消息提示 {"action":"pull", "item_id":"项目id"}
     * 推送消息 {"action":"publish", "to":"用户id", "item_id":"123"}
     * ```
     * @param $data
     * @param Request $request
     * @return array|void
     */
    private function handleMessage($data, Request $request, $ws)
    {
        $action = $data['action'] ?? '';
        if ($action === 'pull') { // 小程序主动拉取
            self::$onlineList['user'][$this->getIdentityId()][$data['item_id']][] = $ws;
            \Co\defer(function() {
                unset(self::$onlineList['user'][$this->getIdentityId()]);
            });

            $allMessage = $this->getAllMessage($data, $request->fd);
            return [
                'code' => 0,
                'data' => $allMessage
            ];
        } elseif ($action === 'publish') { // 广播
            if (stripos($data['to'], ',') !== false) {
                $list = explode(',', $data['to']);
            } else {
                $list = (array)$data['to'];
            }

            foreach ($list as $userId) {
                if (isset(self::$onlineList['user'][$userId][$data['item_id']])) {
                    foreach (self::$onlineList['user'][$userId][$data['item_id']] as $wsConnect) {
                        if ($tmp = $this->getUserMessageTemplate($userId, $data['item_id'])) {
                            $wsConnect->push(json_encode([
                                'code' => 0,
                                'data' => $tmp
                            ]));
                        }
                    }
                }
            }
            return null;
        }
    }

    /**
     * 获取所有消息提醒
     * @param $request
     * @param $fd
     * @return array|mixed|\phpDocumentor\Reflection\Types\Mixed
     */
    private function getAllMessage($request, $fd)
    {
        $tmp = $this->getMessageTemplate($request, $fd);
        foreach ($tmp as &$item) {
            $item['is_message'] = $this->getIsMessage($item['id'], $this->getIdentityId(), $request['item_id']);
        }
        return $tmp;
    }

    private function getUserMessageTemplate($userId, $itemId)
    {
        $template = self::$messageTemplateContext[$userId] ?? null;
        if (!$template) {
            return null;
        }

        foreach ($template as &$item) {
            $item['is_message'] = $this->getIsMessage($item['id'], $userId, $itemId);
        }

        return $template;
    }

    /**
     * 获取用户都有哪些模块
     * @param $request
     * @return array|mixed|\phpDocumentor\Reflection\Types\Mixed
     */
    private function getMessageTemplate($request)
    {
        if (!isset(self::$messageTemplateContext[$this->getIdentityId()])) {
            //获取参数
            $user_id = Laminas::$serviceManager->identity->getId();

            //var_dump('userId', $user_id);
            $item_id = $request['item_id'] ?? 0;
            //var_dump('$item_id', $item_id);
            $user_id = $this->LocalService()->signatoryUser->findsignatoryUser($user_id);
            //查询项目
            $itemInfoID = $this->LocalService()->itemInfo->getOneFieldVal('fid', "is_del = 0 and id = " . $item_id) ?: 0;
            //查询角色下的角色管理用户信息
            $roleSignatoryRelationDatas = $this->LocalService()->roleSignatoryRelation->changeUser($itemInfoID, $user_id);
            //var_dump('//查询角色下的角色管理用户信息', $roleSignatoryRelationDatas);
            if (!empty($roleSignatoryRelationDatas)) {
                $role_id_arr = array_unique(array_column($roleSignatoryRelationDatas, 'role_id'));
                //获取当前模块在当前用户的角色中是有操作的权限
                $whereroleMenuRelation['where'] = "status = 0 and item_id =" . $itemInfoID . " and role_id in (" . implode(',', $role_id_arr) . ")";
                $whereroleMenuRelation['columns'] = ['id', 'module_id'];
                $roleMenuRelationDatas = $this->LocalService()->realRolemodulerelation->fetchAll($whereroleMenuRelation);
                //var_dump('//获取当前模块在当前用户的角色中是有操作的权限', $roleSignatoryRelationDatas);
                if (!empty($roleMenuRelationDatas)) {
                    $module_id_arr = array_unique(array_column($roleMenuRelationDatas, 'module_id'));
                    //获取模块菜单

                    if (!empty($type)) {
                        $whereadminMenu['where'] = "is_del = 0 and  menu_name != '' and  status = 0 and menu_type = 1 and id in (" . implode(',', $module_id_arr) . ")";
                        $whereadminMenu['columns'] = ['id', 'applets_menu' => 'menu_name', 'icon', 'url'];

                    } else {
                        $whereadminMenu['where'] = "is_del = 0 and applets_menu != '' and status = 0 and menu_type = 1 and id in (" . implode(',', $module_id_arr) . ")";
                        $whereadminMenu['columns'] = ['id'];
                    }
                    $whereadminMenu['order'] = ['menu_order'];
                    //var_dump($this->LocalService()->adminMenu->fetchAll($whereadminMenu));
                    self::$messageTemplateContext[$this->getIdentityId()] = $this->LocalService()->adminMenu->fetchAll($whereadminMenu);
                    \Co\defer(function () {
                        unset(self::$messageTemplateContext[$this->getIdentityId()]);
                    });
                }
            }
        }

        return self::$messageTemplateContext[$this->getIdentityId()] ?? [];
    }

    private function getIdentityId(): ?int
    {
        return Laminas::$serviceManager->identity->getId();
    }

    private function getIsMessage($menuId, $userId, $itemId): int
    {
        return Laminas::$serviceManager->redisExtend->setDatabase(6)->getRedisInstance()->getBit("menu:{$itemId}:{$menuId}", $userId) ?: 0;
    }

}