<?php
/**
 * Created by IntelliJ IDEA.
 * User: solu
 * Date: 2019/3/26
 * Time: 5:38 PM
 */

class Telegram {

    const REDIS_PREFIX_TG_CSRF = 'globals:tg_csrf:';
    const REDIS_TG_CSRF_TTL = 60;

    const REDIS_TG_GROUP_TO_GROUP_HASH = 'globals:tg_group_to_group_hash';
    const REDIS_TG_USER_TO_USER_HASH = 'globals:tg_user_to_user_hash';

    const REDIS_TG_MESSAGE_LIST = 'globals:tg_message_list';

    private static $messageTypes = [
        'sticker',
        'photo',
        'audio',
        'video_note',
        'video'
    ];

    /**
     * @param $message
     * @throws Exception
     */
    public static function processMessage($message) {
        // process incoming message
//        $message_id = $message['message_id'];
        $chat_id = $message['chat']['id'];
        $chatType = $message['chat']['type'];
        $from = $message['from']['id'];


        // 处理登录，绑定
        if (strpos($message['text'], '/start') !== false) {
            list($_, $token) = explode(' ', $message['text']);

            $user_id = $from;
            if (strpos($token, 'login') !== false) {

                $name = $message['chat']['first_name'];
                $message['chat']['last_name'] && $name .= "_{$message['chat']['last_name']}";
                $user_id = User::login($from, Account::TYPE_TG, $name);
                if ($user_id) {
                    self::setUserByTG($from, $user_id);
                    self::setCSRFStatus($token, $user_id);
                    $text = 'You\'ve successfully logged in';
                } else {
                    $text = 'Login failure';
                }

            } else if (strpos($token, 'bind') !== false) {

                self::setCSRFStatus($token, $user_id);
                $text = 'You\'ve successfully bind meechat account';

            } else if (strpos($token, 'sync') !== false) { // 群同步流程

                list($_, $groupId) = explode('_', $token);
                $groupId = intval($groupId);
                if (!$groupId) {
                    $text = "Wrong operation!!";
                } else {
                    $creatorId = ThirdApi::getTelegramGroupCreator($groupId);
                    if ($creatorId == $from) {
                        $text = self::buildSyncText($from, $groupId);
                    } else {
                        $text = "you are not creator";
                    }
                }

            } else {
                return;
            }

            self::apiRequest('sendMessage', [
                'chat_id' => $from,
                'text' => $text,
                'parse_mode' => 'HTML',
            ]);
            return;
        }


        // 只转发群消息
        if (!in_array($chatType, ['group', 'supergroup'])) {
            return;
        }

        $objGroup = new GroupInfo();
        $objSession = new Session();

        // 处理升级为超级群的信息
        if (isset($message['migrate_from_chat_id'])) {
            $srcId = $message['migrate_from_chat_id'];

            $groupId = self::getGroupByTG($srcId);
            self::setGroupByTG($chat_id, $groupId);
            self::delGroupByTG($srcId);

            $objGroup->objTable->updateObject(['tg_group_id' => $chat_id], ['tg_group_id' => $srcId]);
            return;
        }

        $groupId = self::getGroupByTG($chat_id);
        if (!$groupId) {

            // 判断是否拉进MeeChat机器人
            $doGuide = false;
            if (isset($message['new_chat_members'])) {
                foreach ($message['new_chat_members'] as $member) {
                    if ($member['id'] == BOT_ID) {
                        $doGuide = true;
                        break;
                    }
                }
            }

            // 创建群的时候已拉入机器人
            if (isset($message['group_chat_created']) && $message['group_chat_created']) {
                $doGuide = true;
            }

            // 开始引导同步操作
            if ($doGuide) {
                $url = BOT_CHAT_URL . "?start=sync_{$chat_id}";
                $text = "点击<a href='{$url}'>@meechatbot</a>查看私聊信息，将该群与MeeChat群组进行关联同步
To sync up this chat with MeeChat group, take a look to private message from <a href='{$url}'>@meechatbot</a>";

                self::apiRequest('sendMessage', [
                    'chat_id' => $chat_id,
                    'text' => $text,
                    'parse_mode' => 'HTML',
                ]);
            }

            return;
        }

        $userId = self::getUserByTG($from);
        !$userId && $userId = TG_BOT_ID;

        $extInfo = [
            'is_tg' => 1,
        ];
        if ($userId == TG_BOT_ID) {
            $name = $message['from']['first_name'];
            $message['from']['last_name'] && $name .= "_{$message['from']['last_name']}";
            $extInfo['tg_nick'] = $name;
        }

        $msgType = Session::MSG_TYPE_TEXT;
        if (isset($message['text'])) { // 文本消息
            $text = $message['text'];

            // 不处理command
            if ($text[0] == '/') {
                return;
            }

        } else { // 图片视频音频

            $mineType = '';
            $fileId = '';
            foreach (self::$messageTypes as $messageType) {
                if (isset($message[$messageType])) {
                    $entity = $message[$messageType];
                    if ($messageType == 'photo') { // 图片会有多个尺寸，取原图
                        $entity = array_pop($entity);
                    }

                    $fileId = $entity['file_id'];
                    $mineType = $message[$messageType]['mime_type'] ?: '';
                    break;
                }
            }

            if (!$fileId) {
                return;
            }

            // 标题
            if ($message['caption']) {
                $caption = $message['caption'];
                $caption = Utils::encodeRC4($caption);
                $objSession->sendGroupMsg($userId, $groupId, Session::MSG_TYPE_TEXT, $caption, false, true, $extInfo);
            }

            list($url, $mineType, $coverUrl) = self::getURLFromFileId($fileId, $mineType);
            $msgType = FileUrl::getType($mineType);
            $text = $url;

            $coverUrl && $extInfo['cover_url'] = $coverUrl;
        }
        $text = Utils::encodeRC4($text);

        $objSession->sendGroupMsg($userId, $groupId, $msgType, $text, false, true, $extInfo);
    }

    /**
     * 转存文件
     * @author solu
     * @param $fileId
     * @param string $mineType
     * @throws Exception
     * @return array
     */
    public static function getURLFromFileId($fileId, $mineType = '') {

        $tgFilePath = self::getFilePath($fileId);
        $url = TELEGRAM_FILE_URL . $tgFilePath;
        list($path, $filename, $_, $_) = Bs2UploadHelper::saveFileFromUrl($url);

        if (!$mineType) {
            $mineType = mime_content_type($path);
        }

        $objFile = new FileUrl();
        $url = $objFile->getFileUrl($path, $filename, $mineType);
        $url = awsReplaceImg($url);

        // 处理视频封面
        $coverUrl = '';
        if (strpos($mineType, 'video') !== false) {
            $coverUrl = $objFile->getVideoCover($path, $filename);
            $coverUrl = awsReplaceImg($coverUrl);
        }

        unlink($path);
        return [$url, $mineType, $coverUrl];
    }

    /**
     * 获取文件路径
     * @param $fileId
     * @return mixed
     * @throws Exception
     */
    private static function getFilePath($fileId) {
        $url = TELEGRAM_API_URL . 'getFile?file_id=' . $fileId;

        $objHttp = new dwHttp();
        $resp = $objHttp->get2($url);
        $data = json_decode($resp, true);
        if (!$data || !$data['ok']) {
            $msg = $data['description'] ?: 'get file info error';
            throw new Exception($msg, CODE_PARAM_ERROR);
        }

        return $data['result']['file_path'];
    }

    /**
     * @param $method
     * @param $parameters
     * @return bool|mixed
     * @throws Exception
     * @return mixed
     */
    public static function apiRequestJson($method, $parameters) {
        if (!is_string($method)) {
            throw new Exception("Method name must be a string\n");
        }

        if (!$parameters) {
            $parameters = array();
        } else if (!is_array($parameters)) {
            throw new Exception("Parameters must be an array\n");
        }

        $parameters["method"] = $method;

        $handle = curl_init(TELEGRAM_API_URL);
        curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($handle, CURLOPT_TIMEOUT, 60);
        curl_setopt($handle, CURLOPT_POST, true);
        curl_setopt($handle, CURLOPT_POSTFIELDS, json_encode($parameters));
        curl_setopt($handle, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));

        return self::exec_curl_request($handle);
    }

    /**
     * @param $method
     * @param $parameters
     * @return mixed
     * @throws Exception
     */
    public static function apiRequest($method, $parameters) {
        if (!is_string($method)) {
            throw new Exception("Method name must be a string\n");
        }

        if (!$parameters) {
            $parameters = array();
        } else if (!is_array($parameters)) {
            throw new Exception("Parameters must be an array\n");
        }

        foreach ($parameters as $key => &$val) {
            // encoding to JSON array parameters, for example reply_markup
            if (!is_numeric($val) && !is_string($val)) {
                $val = json_encode($val);
            }
        }
        $url = TELEGRAM_API_URL.$method.'?'.http_build_query($parameters);

        $handle = curl_init($url);
        curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($handle, CURLOPT_TIMEOUT, 60);

        return self::exec_curl_request($handle);
    }


    /**
     * @param $handle
     * @return bool|mixed
     * @throws Exception
     * @return mixed
     */
    public static function exec_curl_request($handle) {
        $response = curl_exec($handle);

        if ($response === false) {
            $errno = curl_errno($handle);
            $error = curl_error($handle);
            curl_close($handle);
            throw new Exception("Curl returned error $errno: $error\n");
        }

        $http_code = intval(curl_getinfo($handle, CURLINFO_HTTP_CODE));
        curl_close($handle);

        if ($http_code >= 500) {
            // do not wat to DDOS server if something goes wrong
            sleep(10);
            return false;
        } else if ($http_code != 200) {
            $response = json_decode($response, true);
            throw new Exception("Request has failed with error {$response['error_code']}: {$response['description']}\n");
        } else {
            $response = json_decode($response, true);
            if (isset($response['description'])) {
                throw new Exception("Request was successful: {$response['description']}\n");
            }
            $response = $response['result'];
        }

//        file_put_contents('/tmp/telegram_message.log', $response, FILE_APPEND);
        return $response;
    }

    private static function getCSRFKey($token) {
        return self::REDIS_PREFIX_TG_CSRF . $token;
    }

    public static function initCSRF($token) {
        $objRedis = dwRedis::init();

        $key = self::getCSRFKey($token);
        $objRedis->setex($key, self::REDIS_TG_CSRF_TTL, 0);
    }

    /**
     * 获取状态
     * @author solu
     * @param $token
     * @return int
     */
    public static function getCSRFStatus($token) {
        $objRedis = dwRedis::init();

        $key = self::getCSRFKey($token);
        $status = $objRedis->get($key);

        return false !== $status ? intval($status) : -1;
    }

    public static function setCSRFStatus($token, $id) {
        $objRedis = dwRedis::init();

        $key = self::getCSRFKey($token);
        $objRedis->setex($key, self::REDIS_TG_CSRF_TTL, $id);
    }

    public static function setGroupByTG($tgGroup, $group) {
        $objRedis = dwRedis::init();
        $objRedis->hSet(self::REDIS_TG_GROUP_TO_GROUP_HASH, $tgGroup, $group);
    }

    public static function getGroupByTG($tgGroup) {
        $objRedis = dwRedis::init();
        return $objRedis->hGet(self::REDIS_TG_GROUP_TO_GROUP_HASH, $tgGroup);
    }

    public static function delGroupByTG($tgGroup) {
        $objRedis = dwRedis::init();
        return $objRedis->hDel(self::REDIS_TG_GROUP_TO_GROUP_HASH, $tgGroup);
    }

    public static function setUserByTG($tgUser, $user) {
        $objRedis = dwRedis::init();
        $objRedis->hSet(self::REDIS_TG_USER_TO_USER_HASH, $tgUser, $user);
    }

    public static function getUserByTG($tgUser) {
        $objRedis = dwRedis::init();
        return $objRedis->hGet(self::REDIS_TG_USER_TO_USER_HASH, $tgUser);
    }

    public static function delUserByTG($tgUser) {
        $objRedis = dwRedis::init();
        $objRedis->hDel(self::REDIS_TG_USER_TO_USER_HASH, $tgUser);
    }

    public static function pushMessageList($msg) {
        if (is_array($msg)) {
            $msg = json_encode($msg);
        }

        $objRedis = dwRedis::init();
        $objRedis->lPush(self::REDIS_TG_MESSAGE_LIST, $msg);
    }

    public static function popMessageList($objRedis = null) {
        !$objRedis && $objRedis = dwRedis::init();

        $ret = $objRedis->brPop([self::REDIS_TG_MESSAGE_LIST], 30);
        return $ret[1];
    }

    private static function buildSyncText($from, $chat_id) {
        $url = 'https://' . $_SERVER['HTTP_HOST'] . '/#/relateGroup?';
        $params = [
            'admin_id' => $from,
            'group_id' => $chat_id,
            'auth_date' => time(),
        ];
        $params['hash'] = ThirdApi::genTelegramHash($params);
        $url .= http_build_query($params);

        return "<a href='{$url}'>点击选择你需要同步的MeeChat群组
Click to choose your MeeChat group to sync up</a>";
    }
}