<?php

namespace App\Services\User;


use App\Cache\StatisticCache;
use App\Cache\UserCache;
use App\Consts\BaseConst;
use App\Consts\ErrorConst;
use App\Dao\Account\AccountDao;
use App\Dao\User\UserDao;
use App\Facade\Site;
use App\Libs\Utils;
use App\Models\Book\Book;
use App\Models\User\User;
use App\Models\User\UserEarningsLogs;
use App\Models\User\UserPeroidOrderLog;
use App\Models\User\UserShelfBook;
use App\Models\User\WithdrawConfigs;
use App\Services\OpenApi\OpenService;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Vinkla\Hashids\Facades\Hashids;
use App\Exceptions\ApiException;


class UserService
{
    private $userDao;
    private $accountDao;
    private $openService;

    public function __construct(
        UserDao     $userDao,
        AccountDao  $accountDao,
        OpenService $openService
    )
    {
        $this->userDao     = $userDao;
        $this->accountDao  = $accountDao;
        $this->openService = $openService;
    }

    /**
     * 我的书架
     *
     * @param $data
     * @return mixed
     */
    public function getShelfBooks($data)
    {
        $uid = Site::getUid();
        $result = Book::leftJoin('user_shelf_books', 'books.id', 'user_shelf_books.bid')->where('user_shelf_books.uid', $uid)
            ->select('books.id', 'books.name', 'books.cover', 'user_shelf_books.updated_at')->orderBy('user_shelf_books.updated_at', 'desc')->get()->toArray();
        foreach ($result as &$item) {
            $recent_book = UserCache::getRecentBooksByBid($uid, $item['id']);
            if (!$recent_book) {
                $item['read_time'] = transDate($item['updated_at']);
            }else {
                $item['read_time'] = $recent_book['read_time'];
            }
        }
        $result = sortByField($result, 'read_time', 3);
        return $result;
    }

    /**
     * 添加书架
     *
     * @param $data
     * @return mixed
     */
    public function addShelf($data)
    {
        $uid = Site::getUid();

        $bid = getProp($data, 'bid');
        if (mb_strlen($bid) === 32) {
            $bid = Hashids::decode($bid)[0];
        }
        if (!$bid) {
            Utils::throwError(ErrorConst::DATA_EXCEPTION);
        }

        // 统计(uv|pv)
        StatisticCache::setPV('add_shelf');
        if ($uid) StatisticCache::setUV('add_shelf', $uid);

        return UserShelfBook::updateOrCreate([
            'uid' => $uid,
            'bid' => $bid
        ], [
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ]);
    }

    /**
     * 删除书架
     *
     * @param $data
     * @return mixed
     */
    public function batchDeleteShelf($data)
    {
        $uid = Site::getUid();

        $bids = explode(',', getProp($data, 'bids'));
        foreach ($bids as &$bid) {
            if (mb_strlen($bid) === 32) {
                $bid = Hashids::decode($bid)[0];
            }
        }

        if (!$bids) {
            Utils::throwError(ErrorConst::DATA_EXCEPTION);
        }

        return UserShelfBook::where(['uid' => $uid])->whereIn('bid', $bids)->delete();
    }

    /**
     * 最近阅读记录
     *
     * @param $data
     * @return array|mixed
     */
    public function getRecentBooks($data)
    {
        $uid = Site::getUid();

        $recent_books = UserCache::getRecentBooksValues($uid);
        $arr          = [];

        $shelf_bids = UserShelfBook::where('uid', $uid)->whereIn('bid', UserCache::getRecentBooksKeys($uid))
                                   ->select('bid')->get()->pluck('bid')->toArray();

        foreach ($recent_books as $item) {
            if (strtotime($item['read_time']) < time() - BaseConst::ONE_DAY_SECONDS) continue;
            if (in_array($item['bid'], $shelf_bids)) {
                $item['in_shelf'] = 1;
            } else {
                $item['in_shelf'] = 0;
            }
            $item['bid'] = Hashids::encode($item['bid']);
            $item['cid'] = Hashids::encode($item['cid']);
            $arr[]       = $item;
        }
        $arr = sortByField($arr, 'read_time', 3);
        return $arr;
    }

    /**
     * 用户信息
     *
     * @param $data
     * @return array
     */
    public function getUserInfo($data)
    {
        $uid                = Site::getUid();
        $channel_type       = Site::getChannelType();
        $result             = User::where('id', $uid)->select('nickname', 'head_img', 'from_uid', 'vip_limit_date', 'invite_code',
            'alipay_username', 'alipay_account', 'balance', 'charge_balance', 'reward_balance')->first();
        $result             = $result ? $result->toArray() : [];
        $result['vip_limit_date'] = transDate($result['vip_limit_date'], 'Y-m-d');
        if (date('Y-m-d') > $result['vip_limit_date']) $result['vip_limit_date']= '';
        $result['from_uid'] = $result['from_uid'] ? 1 : 0;
        $result['channel_type'] = $channel_type;
        // 对提现账号进行加*处理
        if ($result['alipay_account']) {
            if (strstr($result['alipay_account'], '@')) {
                $result['alipay_account'] = trans_pass($result['alipay_account'], 3);
            }else {
                $result['alipay_account'] = trans_pass($result['alipay_account'], 3, 2);
            }
        }

        return $result;
    }

    /**
     * 绑定用户
     *
     * @param $data
     * @return mixed
     * @throws ApiException
     */
    public function bindUser($data)
    {
        $code = getProp($data, 'invite_code');
        if (!$code) Utils::throwError(ErrorConst::PARAM_ERROR_CODE);
        $uid      = Site::getUid();
        $from_uid = User::where('invite_code', $code)->value('id');
        if ($from_uid) {
            $result = $this->bind($from_uid, $uid);
            if (!is_bool($result)) Utils::throwError('1010:' . $result);
        } else {
            Utils::throwError(ErrorConst::USER_IS_NOT_EXIST);
        }
        return true;
    }

    /**
     * @param $from_uid     int 分享者用户id
     * @param $uid          int 当前登录的用户id
     * @return bool|string
     */
    public function bind($from_uid, $uid)
    {
        if ((int)$from_uid === (int)$uid) return '不得绑定自己';
        if (!$uid) return '当前用户未登录';
        if (User::where('id', $uid)->value('from_uid')) return '您已经绑定了用户,不可重复绑定';

        // 特殊规则(绑定者与被绑定者不可互相成为对方的邀请人)
        if ((int)User::where('id', $from_uid)->value('from_uid') === (int)$uid) return '该用户已绑定';

        try {
            DB::beginTransaction();
            // 更新绑定的用户
            $boolen = User::where('id', $uid)->update([
                'from_uid'   => $from_uid,
                'bind_time'  => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s')
            ]);
            if (!$boolen) {
                Log::error('更新绑定的用户失败: ' . json_encode([
                        'uid'      => $uid,
                        'from_uid' => $from_uid,
                    ], 256));
                DB::rollBack();
                Utils::throwError(ErrorConst::DB_INVALID);
            }

            // 更新邀请人的分享等级
            $invite_num  = User::where('from_uid', $from_uid)->count('id');
            $share_level = DB::table('user_share_configs')->where('need_user_num', '<=', $invite_num)->orderBy('level', 'desc')->value('level');
            $boolen2     = User::where('id', $from_uid)->update([
                'share_level' => $share_level,
                'updated_at'  => date('Y-m-d H:i:s')
            ]);
            if (!$boolen2) {
                Log::error('更新邀请人的分享等级失败: ' . json_encode([
                        'uid'         => $uid,
                        'from_uid'    => $from_uid,
                        'share_level' => $share_level,
                    ], 256));
                DB::rollBack();
                Utils::throwError(ErrorConst::DB_INVALID);
            }

            // 分享量达到指定量后送一天会员
            $share_num = UserCache::getTodayShareNum($from_uid);
            $redis_key = 'vip_sended_' . $from_uid;
            if (!Redis::exists($redis_key) && ($share_num + 1) >= BaseConst::DAILY_SHARE_NUM) {     // 今日未赠送会员并且分享数超过目标值
                $send_vip_day = 1;
                $from_uid_info = User::find($from_uid);
                $origin_end_time = getProp($from_uid_info, 'vip_limit_date');
                if (!$origin_end_time) $origin_end_time = date('Y-m-d H:i:s');
                $vip_limit_date = date('Y-m-d 23:59:59', strtotime($origin_end_time . ' +'.$send_vip_day.' day'));
                $boolen3        = User::where('id', $from_uid)->update([
                    'vip_limit_date' => $vip_limit_date,
                    'updated_at'     => date('Y-m-d H:i:s')
                ]);
                if (!$boolen3) {
                    Log::error('更新邀请人的会员有效期失败: ' . json_encode([
                            'uid'            => $uid,
                            'from_uid'       => $from_uid,
                            'vip_limit_date' => $vip_limit_date,
                        ], 256));
                    DB::rollBack();
                    Utils::throwError(ErrorConst::DB_INVALID);
                }

                $log_data = [
                    'uid'                       => $from_uid,
                    'distribution_channel_id'   => getProp($from_uid_info, 'distribution_channel_id'),
                    'fee'                       => 0,
                    'add_type'                  => 'share',
                    'send_order_id'             => getProp($from_uid_info, 'send_order_id'),
                    'add_days'                  => $send_vip_day,
                    'trade_no'                  => '',
                    'origin_end_time'           => $origin_end_time,
                    'end_time'                  => $vip_limit_date,
                    'vip_type'                  => '',
                    'desc'                      => '完成分享任务赠送'.$send_vip_day.'天会员',
                ];
                $boolen4 = UserPeroidOrderLog::firstOrCreate($log_data);
                if (!$boolen4) {
                    Log::error('会员有效期日志更新失败: ' . json_encode($log_data, 256));
                    DB::rollBack();
                    Utils::throwError(ErrorConst::DB_INVALID);
                }

                // 每日仅可享受一次赠送
                $expire_at = strtotime(date('Y-m-d 23:59:59')) - time();
                Redis::setex($redis_key, $expire_at, 1);
            }

            // 更新当日分享量
            UserCache::setTodayShareNum($from_uid);

            // 更新redis保存的from_uid
            $token_data             = UserCache::getTokenData(Site::getToken());
            $token_data['from_uid'] = $from_uid;
            UserCache::setTokenData(Site::getToken(), $token_data);

            DB::commit();
        } catch (Exception $e) {
            DB::rollBack();
            return $e->getMessage();
        }
        return true;
    }

    /**
     * 绑定派单链接
     * @param $send_order_id
     * @return int
     * @throws ApiException
     */
    public function bindSendOrder($send_order_id) {
        $send_order = DB::table('send_orders')->where('id', $send_order_id)->first();
        if (!$send_order) return "该派单链接不存在,请确认后重试~";

        $uid = Site::getUid();
        $user_info = DB::table('users')->where('id', $uid)->select('distribution_channel_id', 'send_order_id')->first();
        if (!$user_info) return '该用户不存在';

        if (getProp($user_info, 'send_order_id')) return '该用户已绑定过派单链接';
        return DB::table('users')->where('id', $uid)->update([
            'distribution_channel_id'   => getProp($send_order, 'distribution_channel_id'),
            'send_order_id' => getProp($send_order, 'id'),
            'updated_at'    => date('Y-m-d H:i:s')
        ]);
    }

    /**
     * 福利页
     *
     * @param $data
     * @return array
     */
    public function welfare($data)
    {
        $uid                  = Site::getUid();
        $result               = User::leftJoin('user_share_configs', 'user_share_configs.id', 'users.share_level')->where('users.id', $uid)
                                    ->select('users.total_earnings', 'users.enable_withdraw_earnings', 'users.share_level', 'user_share_configs.share_percent',
                                        DB::raw("(select need_user_num from user_share_configs as us2 where us2.level > users.share_level order by level limit 1) as need_user_num"),
                                        DB::raw("(select count(id) from users as u2 where u2.from_uid = users.id) as invite_user_num"))->first();
        $result               = $result ? $result->toArray() : [];
        $result['next_level'] = $result['share_level'] + 1;
        // 不得超过最高等级
        $max_level = DB::table('user_share_configs')->orderBy('level', 'desc')->value('level');
        if ($result['next_level'] >= $max_level) $result['next_level'] = $max_level;

        // 升级所需人数
        $result['upgrade_percent'] = $result['need_user_num'] ? round($result['invite_user_num'] / $result['need_user_num'] * 100) : 0;
        $result['need_user_num']   = $result['need_user_num'] ? strval($result['need_user_num'] - $result['invite_user_num']) : -1;
        $result['share_percent']   = round($result['share_percent'] * 100) . '%';

        // 今日分享
        $result['today_share_num'] = UserCache::getTodayShareNum($uid);
        // 当前阅读书籍
        $current_chapter = UserCache::getCurrentChapter($uid);
        if ($current_chapter) {
            $result['bid'] = Hashids::encode($current_chapter['bid']);
        }

        // 统计(uv|pv)
        StatisticCache::setPV('welfare');
        if ($uid) StatisticCache::setUV('welfare', $uid);

        return $result;
    }

    /**
     * 我的收益
     *
     * @param $data
     * @return array
     */
    public function earnings($data)
    {
        $uid                       = Site::getUid();
        $result                    = [];
        $result['earnings']        = User::where('id', $uid)->select('total_earnings', 'enable_withdraw_earnings')->first();
        $result['earnings']        = $result['earnings'] ? $result['earnings']->toArray() : [];
        $result['earning_records'] = UserEarningsLogs::leftJoin('users', 'users.id', 'user_earnings_logs.from_uid')
            ->where('user_earnings_logs.uid', $uid)->orderBy('user_earnings_logs.created_at', 'desc')->select('user_earnings_logs.*', 'users.nickname')->get()->toArray();
        $result['invite_records']  = User::where('from_uid', $uid)->orderBy('bind_time', 'desc')->select('nickname', 'bind_time')->get()->toArray();
        return $result;
    }

    /**
     * 提现档位
     *
     * @param $data
     * @return array
     */
    public function withdrawConfigs($data)
    {
        $uid    = Site::getUid();
        $result = WithdrawConfigs::select('withdraw_type', 'price')->orderBy('sort', 'asc')->get()->toArray();
        $list   = [];
        $user   = User::where('id', $uid)->select('is_new', 'total_earnings', 'enable_withdraw_earnings')->first();
        if (!$user) Utils::throwError(ErrorConst::USER_IS_NOT_EXIST);
        $user              = $user->toArray();
        $new_user          = ['price' => '1.00', 'info' => '新人特权'];
        $withdraw_type_arr = [
            'WECHAT'   => '微信提现',
            'ZHIFUBAO' => '支付宝提现'
        ];
        foreach ($result as $item) {
            if (!isset($list[$item['withdraw_type']])) {
                $list[$item['withdraw_type']] = [
                    'withdraw_type'  => $item['withdraw_type'],
                    'withdraw_info'  => isset($withdraw_type_arr[$item['withdraw_type']]) ? $withdraw_type_arr[$item['withdraw_type']] : '',
                    'withdraw_price' => [
                        [
                            'price' => $item['price'],
                            'info'  => ''
                        ],
                    ]
                ];
                if ($user['is_new']) array_unshift($list[$item['withdraw_type']]['withdraw_price'], $new_user);
            } else {
                $list[$item['withdraw_type']]['withdraw_price'][] = ['price' => $item['price'], 'info' => ''];
            }
        }
        $arr['withdraw_data'] = array_values($list);
        $arr['earnings']      = $user;

        return $arr;
    }

    /**
     * 提现
     *
     * @param $data
     * @return mixed
     * @throws ApiException
     */
    public function withdraw($data)
    {
        $amount          = getProp($data, 'amount');
        $withdraw_type   = getProp($data, 'withdraw_type');
        $alipay_username = getProp($data, 'alipay_username');
        $alipay_account  = getProp($data, 'alipay_account');
        $uid             = Site::getUid();
        $user            = User::find($uid);
        // 验证alipay_account
        if (strstr($alipay_account, '@')) {
            // 如果传参的邮箱和加*的一致则用户使用的是上次提现账号
            if ($alipay_account == trans_pass($user->alipay_account, 3)) {
                $alipay_account = $user->alipay_account;
            }
//            // 验证手机号
//            if (!preg_match('/^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\d{8}$/', $alipay_account)) Utils::throwError('1002:手机号格式不正确');
        } else {
            // 如果传参的手机号和加*的一致则用户使用的是上次提现账号
            if ($alipay_account == trans_pass($user->alipay_account, 3, 2)) {
                $alipay_account = $user->alipay_account;
            }
            // 验证邮箱
//            if (!preg_match('/^[_.0-9a-z-]+@([0-9a-z][0-9a-z-]+.)+[a-z]{2,3}$/', $alipay_account)) Utils::throwError('1002:邮箱格式不正确');
        }

        if ($amount == 1) {
            if (!$user->is_new) Utils::throwError('1002:不支持的提现额度');
        } else {
            $access_amount = WithdrawConfigs::where('withdraw_type', $withdraw_type)->get()->pluck('price')->toArray();
            if (!in_array($amount, $access_amount)) Utils::throwError('1002:不支持的提现额度');
        }
        if ($amount > $user->enable_withdraw_earnings) Utils::throwError('1002:您的余额不足,无法提现');

        $distribution_channel_info = DB::table('distribution_channels')->where('id', Site::getChannelId())->first();
        if (!$distribution_channel_info) Utils::throwError(ErrorConst::DATA_EXCEPTION);
        // 写入申请提现表
        $withdraw_data = [
            'uid'                       => $uid,
            'distribution_channel_id'   => $distribution_channel_info->id,
            'distribution_channel_name' => $distribution_channel_info->name,
            'amount'                    => $amount,
            'status'                    => '待审核',
            'withdraw_type'             => $withdraw_type,
            'withdraw_info'             => '',
            'alipay_username'           => $alipay_username,
            'alipay_account'            => $alipay_account,
            'created_at'                => date('Y-m-d H:i:s'),
            'updated_at'                => date('Y-m-d H:i:s'),
        ];

        // 写入收益变动日志
        $withdraw_log = [
            'uid'           => $uid,
            'from_uid'      => 0,
            'charge_price'  => 0,
            'share_percent' => 0,
            'withdraw_info' => '提现',
            'change_type'   => 2,
            'before_amount' => $user->enable_withdraw_earnings,
            'change_amount' => $amount,
            'after_amount'  => round(($user->enable_withdraw_earnings - $amount), 2),
            'created_at'    => date('Y-m-d H:i:s'),
            'updated_at'    => date('Y-m-d H:i:s')
        ];

        try {
            DB::beginTransaction();
            $boolen = DB::table('user_withdraw_cashes')->insert($withdraw_data);
            if (!$boolen) {
                Log::error('申请提现失败, 错误信息: ' . json_encode($withdraw_data, 256));
                DB::rollBack();
                Utils::throwError(ErrorConst::DB_INVALID);
            }

            $boolen1 = UserEarningsLogs::create($withdraw_log);
            if (!$boolen1) {
                Log::error('写入收益变动日志失败, 错误信息: ' . json_encode($withdraw_log, 256));
                DB::rollBack();
                Utils::throwError(ErrorConst::DB_INVALID);
            }

            $user->enable_withdraw_earnings -= $amount;
            $user->alipay_username = $alipay_username;
            $user->alipay_account = $alipay_account;
            $user->is_new = 0;
            $boolen2 = $user->save();
            if (!$boolen2) {
                Log::error('用户可提现收益更新失败, 错误信息: ' . json_encode($user, 256));
                DB::rollBack();
                Utils::throwError(ErrorConst::DB_INVALID);
            }
            DB::commit();
        } catch (Exception $e) {
            DB::rollBack();
            Utils::throwError('1010:' . $e->getMessage());
        }
        return true;
    }

    /**
     * 同步用户数据
     *
     * @param string $iv
     * @param string $signature
     * @param string $encryptedData
     * @param string $rawData
     * @param        $userInfo
     * @return bool
     * @throws ApiException
     */
    public function syncUserInfo(string $iv, string $signature, string $encryptedData, string $rawData, $userInfo): bool
    {
        $uid        = Site::getUid();
        $user       = $this->userDao->getUserById($uid);
        $sessionKey = getProp($user, 'session_key');
        $openId     = getProp($user, 'openid');
        if (empty($user)) {
            Utils::throwError(ErrorConst::USER_IS_NOT_EXIST);
        }
        dLog('user')->info('syncUserInfo-params', compact('uid', 'sessionKey', 'openId', 'iv', 'signature', 'encryptedData', 'rawData', 'userInfo'));

        // 获取用户session_key并初始化
        // $instance = $this->openService->getInstance();

        /*
        // 校验用户数据有效性
        if ($signature && $rawData) {
            $isValid = $instance->checkUserDataValid($rawData, $sessionKey, $signature);
            if (!$isValid) {
                Utils::throwError(ErrorConst::USER_DATA_IS_VALID);
            }
        }

        // 解密数据
        $decryptedData = json_decode($rawData, true);
        if ($encryptedData && $iv) {
            $decryptedData = $instance->decryptUserData($encryptedData, $sessionKey, $iv);
        }
        dLog('user')->info('syncUserInfo-decryptedData', compact('uid', 'decryptedData'));
        */

        $decryptedData = $userInfo;
        $updateData    = ['updated_at' => date('Y-m-d H:i:s')];
        if (isset($decryptedData['avatarUrl'])) $updateData['head_img'] = $decryptedData['avatarUrl'];
        if (isset($decryptedData['nickName'])) $updateData['nickname'] = $decryptedData['nickName'];
        if (isset($decryptedData['gender'])) $updateData['sex'] = $this->transGender((int)$decryptedData['gender']);
        if (isset($decryptedData['city']) && $decryptedData['city']) $updateData['city'] = (string)$decryptedData['city'];
        if (isset($decryptedData['province']) && $decryptedData['province']) $updateData['province'] = (string)$decryptedData['province'];
        if (isset($decryptedData['country']) && $decryptedData['province']) $updateData['country'] = (string)$decryptedData['country'];
        if (count($updateData) < 2) {
            return false;
        }

        /*
        // 用户openid判断匹配
        if ($openId != getProp($decryptedData, 'openid')) {
            return false;
        }

        // appid判断匹配
        $waterMark = getProp($decryptedData, 'watermark', []);
        $appId     = getProp($waterMark, 'appid');
        $minApp    = $this->accountDao->getMiniAppByAppId($appId);
        if ($appId != getProp($minApp, 'appid')) {
            return false;
        }
        */

        return $this->userDao->updateUserById($uid, $updateData);
    }

    /**
     * 转换性别
     *
     * @param int $gender
     * @return int
     */
    private function transGender(int $gender): int
    {
        $sex = 2;
        if ($gender == 1) {
            $sex = 0;
        }
        if ($gender == 2) {
            $sex = 1;
        }

        return $sex;
    }
}
