<?php

namespace App\Services\OpenApi;

use App\Cache\UserCache;
use App\Consts\ErrorConst;
use App\Consts\SysConst;
use App\Dao\Channel\ChannelDao;
use App\Dao\SendOrder\SendOrderDao;
use App\Dao\User\UserDao;
use App\Libs\TikTok\Application;
use App\Libs\TikTok\MiniProgram\Application as MiniProgramApplication;
use App\Libs\TikTok\OpenPlatform\Application as OpenPlatformApplication;
use App\Libs\TikTok\Kernel\Exceptions;
use App\Libs\Utils;
use GuzzleHttp\Exception\GuzzleException;
use App\Exceptions\ApiException;

class OpenService
{
//    private MiniProgramApplication  $app;
//    private OpenPlatformApplication $openApp;
    private $app;
    private $openApp;
    private $userDao;
    private $channelDao;
    private $sendOrderDao;

    public function __construct(
        UserDao      $userDao,
        ChannelDao   $channelDao,
        SendOrderDao $sendOrderDao
    )
    {
        $this->userDao      = $userDao;
        $this->channelDao   = $channelDao;
        $this->sendOrderDao = $sendOrderDao;
    }

    /**
     * 实例化小程序
     *
     * @param array $param
     * @return $this
     */
    public function getInstance(array $param = []): OpenService
    {
        // 获取配置
        $config    = $this->getConfig($param);
        $this->app = Application::miniProgram($config);
        return $this;
    }

    /**
     * 实例化小程序
     *
     * @param array $param
     * @return $this
     */
    public function getOpenInstance(array $param = []): OpenService
    {
        // 获取配置
        $config        = $this->getConfig($param);
        $this->openApp = Application::openPlatform($config);
        return $this;
    }

    /**
     * @param $param
     * @return array
     */
    private function getConfig($param): array
    {
        $config = [
            'app_id' => env('DOUYIN_APP_ID'),
            'secret' => env('DOUYIN_APP_SECRET'),
            'cache'  => [
                'type'      => 'Redis',
                'host'      => env('REDIS_HOST'),
                'port'      => env('REDIS_PORT'),
                'password'  => env('REDIS_PASSWORD'),
                'select'    => env('REDIS_DB'),
                'prefix'    => '',
                'serialize' => []
            ],
        ];

        // 基本参数
        $appId  = getProp($param, 'app_id');
        $secret = getProp($param, 'secret');
        if ($appId) $config['app_id'] = $appId;
        if ($secret) $config['secret'] = $secret;

        // 沙箱模式
        if (getProp($param, 'sandbox')) {
            $config['http'] = [
                'base_uri' => 'https://open-sandbox.douyin.com/api/',
            ];
        }

        return $config;
    }

    /**
     * @param bool $refresh
     * @return array
     * @throws Exceptions\HttpException
     * @throws Exceptions\InvalidArgumentException
     * @throws Exceptions\InvalidConfigException
     * @throws Exceptions\RuntimeException
     * @throws GuzzleException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function getAccessToken(bool $refresh = false): array
    {
        $result = $this->app->access_token->getToken($refresh);
        dLog('openApi')->info('getAccessToken', $result);
        return $result;
    }

    /**
     * 登录
     *
     * @param int    $sendOrderId
     * @param string $code
     * @param string $anonymousCode
     * @return array
     * @throws ApiException
     * @throws GuzzleException
     */
    public function code2Session(int $sendOrderId, string $code, string $anonymousCode = '')
    {
        dLog('openApi')->info('code2Session', compact('sendOrderId', 'code', 'anonymousCode'));
        try {
            $result = $this->app->auth->session($code, $anonymousCode);
        } catch (\Exception $e) {
            dLog('openApi')->info('code2Session-exception', [$e->getMessage()]);
            Utils::throwError(ErrorConst::SYS_EXCEPTION);
        }
        dLog('openApi')->info('code2Session-result', compact('result'));

        // 解析返回值
        $resultData    = getProp($result, 'data', []);
        $unionId       = getProp($resultData, 'unionid');
        $openId        = getProp($resultData, 'openid');
        $newSessionKey = getProp($resultData, 'session_key');

        // 判断返回信息
        if (empty($openId) || empty($unionId)) {
            Utils::throwError(ErrorConst::USER_AUTH_FAIL);
        }

        // 获取派单信息（派单对应的站点id即为用户注册的站点id，无派单，则设置默认站点id）
        $sendOrder          = $this->sendOrderDao->getSendOrderById($sendOrderId);
        $sendOrderChannelId = (int)getProp($sendOrder, 'distribution_channel_id', SysConst::DEFAULT_CHANNEL_ID);

        // 获取用户数据
        $user            = $this->userDao->getUserByOpenIdAndUnionId($openId, $unionId);
        $uid             = (int)getProp($user, 'id');
        $oldSessionKey   = getProp($user, 'session_key');
        $userSendOrderId = (int)getProp($user, 'send_order_id', $sendOrderId);
        $channelId       = (int)getProp($user, 'distribution_channel_id', $sendOrderChannelId);
        dLog('openApi')->info('code2Session-user', compact('uid', 'oldSessionKey'));

        // 计算token
        $isNewUser = false;
        [$oldToken, $newToken] = [md5($oldSessionKey), md5($newSessionKey)];
        if (empty($user)) {
            // 新建用户
            $user = $this->userDao->createUser([
                'openid'                  => $openId,
                'unionid'                 => $unionId,
                'session_key'             => $newSessionKey,
                'token'                   => $newToken,
                'distribution_channel_id' => $channelId,
                'bind_time'               => date('Y-m-d H:i:s'),
                'sex'                     => 2,
                'register_ip'             => _getIp()
            ]);

            // 新uid
            $uid = getProp($user, 'id');
            if (!$uid) {
                Utils::throwError(ErrorConst::USER_INI_FAIL);
            }

            // 更新用户名
            $user->nickname    = "用户" . $uid;
            $user->invite_code = (int)$uid + 100000;
            $user->save();

            // 新用户标识
            $isNewUser = true;
        } else {
            // 更新用户数据
            $this->userDao->updateUserById($uid, [
                'session_key' => $newSessionKey,
                'token'       => $newToken,
                'invite_code' => $uid + 100000,
                'updated_at'  => date('Y-m-d H:i:s')
            ]);
        }

        // 更新redis token数据
        UserCache::setTokenData($newToken, [
            'uid'                     => $uid,
            'distribution_channel_id' => $channelId,
            'send_order_id'           => $userSendOrderId,
            'from_uid'                => (int)getProp($user, 'from_uid'),
        ]);
        UserCache::delTokenData($oldToken);

        // 获取注册用户站点信息
        $channel = $this->channelDao->getChannelById($channelId);

        // 全局绑定（后续绑定逻辑需要用到这些字段）
        $site               = app('siteData');
        $site->uid          = (int)$uid;
        $site->token        = $newToken;
        $site->channel_type = getProp($channel, 'channel_type', 'PERIOD');

        return ['uid' => $uid, 'token' => $newToken, 'distribution_channel_id' => $channelId, 'is_new_user' => $isNewUser];
    }

    /**
     * @param string $rawData
     * @param string $sessionKey
     * @param string $signature
     * @return bool
     */
    public function checkUserDataValid(string $rawData, string $sessionKey, string $signature): bool
    {
        if ($signature != sha1($rawData . $sessionKey)) {
            return false;
        }

        return true;
    }

    /**
     * 解密用户数据
     *
     * @param string $encryptedData
     * @param string $sessionKey
     * @param string $iv
     * @return array
     */
    public function decryptUserData(string $encryptedData, string $sessionKey, string $iv): array
    {
        if (empty($encryptedData) || empty($sessionKey) || empty($iv)) {
            return [];
        }

        try {
            $result = $this->app->encryptor->decryptData($sessionKey, $iv, $encryptedData);
        } catch (\Exception $e) {
            dLog('openApi')->info('decryptUserData-exception', [$e->getMessage()]);
            return [];
        }

        dLog('openApi')->info('decryptUserData', $result);
        return $result;
    }

    /**
     * 获取小程序端内分享链接
     *
     * @param string $path
     * @param string $query
     * @return array
     * @throws GuzzleException
     */
    public function generateShareLink(string $path = '', string $query = ''): array
    {
        try {
            $result = $this->app->share->generateLink($path, $query);
        } catch (\Exception $e) {
            dLog('openApi')->info('generateShareLink-exception', [$e->getMessage()]);
            return [];
        }

        dLog('openApi')->info('generateShareLink', $result);
        return $result;
    }

    /**
     * 查询已经生成的 link 的信息
     *
     * @param string $url_link
     * @return array
     */
    public function getShareQueryInfo(string $url_link = '')
    {
        try {
            $result = $this->app->share->getQueryInfo($url_link);
        } catch (\Exception $e) {
            dLog('openApi')->info('getShareQueryInfo-exception', [$e->getMessage()]);
            return [];
        }

        dLog('openApi')->info('getShareQueryInfo', $result);
        return $result;
    }

    public function createQRCode(string $path = '')
    {
        try {
            $result = $this->app->share->createQRCode($path);
        } catch (\Exception $e) {
            dLog('openApi')->info('createQRCode-exception', [$e->getMessage()]);
            return [];
        }

        dLog('openApi')->info('createQRCode', $result);
        return $result;
    }

    /**
     * @param string $openId
     * @param int    $orderStatus
     * @param array  $orderDetail
     * @return array
     * @throws GuzzleException
     */
    public function pushOrder(string $openId, int $orderStatus, array $orderDetail): array
    {
        try {
            $result = $this->app->order->push($openId, $orderStatus, $orderDetail);
        } catch (\Exception $e) {
            dLog('openApi')->info('pushOrder-exception', [$e->getMessage()]);
            return [];
        }

        dLog('openApi')->info('pushOrder', $result);
        return $result;
    }

    /**
     * 查询抖音开放能力列表
     *
     * @return array
     */
    public function permissionList(): array
    {
        try {
            $result = $this->app->capacity->queryPermissionList();
        } catch (\Exception $e) {
            dLog('openApi')->info('permissionList-exception', [$e->getMessage()]);
            return [];
        }

        dLog('openApi')->info('permissionList', $result);
        return $result;
    }

    /**
     * @param bool $refresh
     * @return array
     * @throws Exceptions\HttpException
     * @throws Exceptions\InvalidArgumentException
     * @throws Exceptions\InvalidConfigException
     * @throws Exceptions\RuntimeException
     * @throws GuzzleException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function getClientToken(bool $refresh = false): array
    {
        $result = $this->openApp->access_token->getToken($refresh);
        dLog('openApi')->info('getClientToken', $result);
        return $result;
    }

    /**
     * @param string $serviceOpenId 客服号openId
     * @param array  $data
     * @return array
     * @throws GuzzleException
     */
    public function senImMsg(string $serviceOpenId, array $data): array
    {
        dLog('openApi')->info('senImMsg-params', compact('serviceOpenId', 'data'));
        try {
            $result = $this->openApp->im->sendMsg($serviceOpenId, $data);
        } catch (\Exception $e) {
            dLog('openApi')->info('senImMsg-exception', [$e->getMessage()]);
            return [];
        }

        dLog('openApi')->info('senImMsg', $result);
        return $result;
    }

    /**
     * @return array
     */
    public function eventList()
    {
        try {
            $result = $this->openApp->event->eventList();
        } catch (\Exception $e) {
            dLog('openApi')->info('eventList-exception', [$e->getMessage()]);
            return [];
        }

        dLog('openApi')->info('eventList', $result);
        return $result;
    }
}
