<?php

namespace App\Repositories\Customers;

use App\Modules\Enums\PlatformType;
use App\Modules\Repositories\Customer\BaseCustomerRepository;
use App\Common\Alipay;
use App\Common\Qiniu;
use App\Common\Sms;
use App\Modules\Enums\AlipayLoginType;
use App\Modules\Enums\ErrorCode;
use App\Modules\Enums\LoginType;
use App\Modules\Enums\SmsType;
use App\Exceptions\Api\ApiException;
use App\Modules\Models\Customer\Customer;
use App\Util\AuthUtil;
use App\Util\CustomerUtil;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;

/**
 * Class EloquentHistoryRepository.
 */
class CustomerRepository extends BaseCustomerRepository
{
    /**
     * @return string
     */
    private function guardStr()
    {
        return "api";
    }

    /**
     * @param $id
     * @return mixed
     */
    public function retrieveById($id)
    {
        return Customer::find($id);
    }

    /**
     * @param $input
     * @return bool
     * @throws ApiException
     */
    public function create($input)
    {

        $customer = $this->createCustomerStub($input);
        DB::beginTransaction();
        try {
            $customer->save();

            DB::commit();
        } catch (\Exception $e) {

            DB::rollBack();
            throw new ApiException(ErrorCode::DATABASE_ERROR, trans('api.error.database_error'));
        }

        return $customer;
    }

    /**
     * @param Customer $customer
     * @param $input
     * @return bool
     * @throws ApiException
     */
    public function update(Customer $customer, $input)
    {
        Log::info("update param:" . json_encode($input));

        if ($customer->update($input)) {
            return true;
        }

        throw new ApiException(ErrorCode::DATABASE_ERROR, trans('api.error.database_error'));
    }

    /**
     * @param $input
     * @return bool
     * @throws ApiException
     */
    public function updatePassword($input)
    {
        $customer = Customer::where('telephone', $input['telephone'])->first();
        $customer->password = bcrypt($input['password']);

        if ($customer->save()) {
            return true;
        }

        throw new ApiException(ErrorCode::DATABASE_ERROR, trans('api.error.database_error'));
    }

    /**
     * @param $credentials
     * @return mixed
     */
    public function wechat_miniprogram_login($credentials)
    {
        $customer = Customer::where('mini_program_open_id', $credentials['openId'])->first();

        //if not exist, add the account
        if ($customer == null) {
            $input = [
                'mini_program_open_id' => $credentials['openId'],
                'nick_name' => $credentials['nickName'],
                'gender' => $credentials['gender'],
                'language' => $credentials['language'],
                'avatar_url' => $credentials['avatarUrl'],
                'platform' => PlatformType::WECHAT,
                //todo 测试环境无unionid暂时注释
//                'unionid' => $credentials['unionid'],
            ];
            $customer = $this->create($input);
        }

        if (empty($customer->mini_program_open_id) && isset($credentials['openId'])) {
            $customer->mini_program_open_id = $credentials['openId'];
            $customer->save();
        }

        return $this->login(array('mini_program_open_id' => $credentials['openId']));
    }

    /**
     * @param $credentials
     * @return \Illuminate\Http\JsonResponse
     * @throws ApiException
     */
    public function loginViaAlipay($credentials)
    {
        $alipay = new Alipay();

        $alipay_login_type = AlipayLoginType::APP;
        if (isset($credentials['type'])) {
            $alipay_login_type = $credentials['type'];
        }

        $response = $alipay->alipaySystemAccountAuth($credentials['code'], $alipay_login_type);

        if (!$response) {
            throw new ApiException(ErrorCode::OPEN_ID_ACCESS_TOKEN_ERROR, trans('api.error.open_id_access_token_failed'));
        }

        $customer = Customer::where('alipay', $response['user_id'])->first();


        //if not exist, add the account
        if ($customer == null) {
            $response = $alipay->alipayUserInfoShare($response['access_token'], $alipay_login_type);
            $input['username'] = isset($response['nick_name']) ? $response['nick_name'] : '';
            $input['head_image'] = isset($response['avatar']) ? $response['avatar'] : '';
            $input['alipay'] = isset($response['user_id']) ? $response['user_id'] : '';
            $input['gender'] = isset($response['gender']) ? ($response['gender'] == 'F' ? 1 : 0) : 0;
            $input['type'] = LoginType::ALIPAY;

            $customer = $this->create($input);
        }

        return $this->login(array('alipay' => $customer->alipay));
    }

    /**
     * @param $credentials
     * @return \Illuminate\Http\JsonResponse
     * @throws ApiException
     */
    public function login($credentials)
    {
        try {
            // attempt to verify the credentials and create a token for the user
            if (!$token = Auth::attempt($credentials)) {
                throw new ApiException(ErrorCode::LOGIN_FAILED, trans('api.error.login_failed'));
            }
        } catch (JWTException $e) {
            // something went wrong whilst attempting to encode the token
            throw new ApiException(ErrorCode::CREATE_TOKEN, trans('api.error.create_token_failed'));
        }

        $user = Auth::User();
        $user->last_login_at = Carbon::now();
        $user->save();

        $expire = Auth::setToken($token)->getPayload()->get('exp');
        $token = 'Bearer ' . $token;

        $customer_id = $user->id;
        // all good so return the token
        return compact('token', 'expire','customer_id');
    }

    /**
     * @return array
     * @throws ApiException
     */
    public function refreshToken()
    {
        try {
            $old_token = Auth::getToken();
            $token = Auth::refresh($old_token);
        } catch (TokenExpiredException $e) {
            throw new ApiException(ErrorCode::TOKEN_EXPIRE, trans("api.error.token_expire"), array());
        } catch (JWTException $e) {
            throw new ApiException(ErrorCode::TOKEN_INVALID, trans("api.error.token_invalid"), array());
        }

        $expire = Auth::setToken($token)->getPayload()->get('exp');
        $token = 'Bearer ' . $token;

        // all good so return the token
        return compact('token', 'expire');
    }

    /**
     * @return array
     * @throws ApiException
     */
    private function checkTokenInternal()
    {
        $isValid = true;
        try {
            $token = Auth::getToken();
            AuthUtil::checkOrFail($this->guardStr());
        } catch (TokenExpiredException $e) {
            $isValid = false;
            try {
                $token = Auth::refresh();
                Auth::setToken($token);
                AuthUtil::checkOrFail($this->guardStr());
            } catch (TokenExpiredException $e) {
                throw new ApiException(ErrorCode::TOKEN_EXPIRE, trans("api.error.token_expire"), array());
            } catch (JWTException $e) {
                throw new ApiException(ErrorCode::TOKEN_INVALID, trans("api.error.token_invalid"), array());
            }
        } catch (JWTException $e) {
            throw new ApiException(ErrorCode::TOKEN_INVALID, trans("api.error.token_invalid"), array());
        }

        $expire = Auth::setToken($token)->getPayload()->get('exp');
        $token = 'Bearer ' . $token;

        // all good so return the token
        return $isValid ? array() : compact('token', 'expire');
    }


    /**
     * @return array
     */
    public function checkToken()
    {
        $ret = $this->checkTokenInternal();

        $user = Auth::User();
        $user->last_login_at = Carbon::now();
        $user->save();

        return $ret;
    }

    /**
     * @return mixed
     */
    public function logout()
    {
        Auth::invalidate();
        return true;
    }

    /**
     * @param $input
     * @throws ApiException
     * @return boolean
     */
    public function sms($input)
    {
        $type = $input['type'];
        $telephone = $input['telephone'];

        switch ($type) {
            //register case
            case SmsType::REGISTER:
                if (CustomerUtil::telephoneExist($telephone)) {
                    throw new ApiException(ErrorCode::USER_ALREADY_EXIST, trans('api.error.user_already_exist'));
                }
                break;
            //reset password case
            case SmsType::RESET_PASSWORD:
                if (!CustomerUtil::telephoneExist($telephone)) {
                    throw new ApiException(ErrorCode::USER_NOT_EXIST, trans('api.error.user_not_exist'));
                }
                break;
            case SmsType::SMS_LOGIN:
                if (!CustomerUtil::telephoneExist($telephone)) {
                    $customer = $this->create(array('telephone' => $telephone));
                }
                break;
            case SmsType::BIND:
                if (CustomerUtil::telephoneExist($telephone)) {
                    throw new ApiException(ErrorCode::USER_ALREADY_EXIST, trans('api.error.user_already_exist'));
                }
                break;
            default:
                throw new ApiException(ErrorCode::SMS_INVALID_TYPE, trans('api.error.sms_invalid_type'));
                break;
        }

        $result = Sms::sendCode($telephone);

        if (!$result) {
            throw new ApiException(ErrorCode::SMS_ERROR, trans('api.error.sms_error'));
        }

        return true;
    }

    /**
     * @param Customer $customer
     * @return string
     */
    public function headImageUploadToken(Customer $customer)
    {
        $policy = array(
            'callbackUrl' => config("constants.qiniu.callback_base_url") . '/customers/headerUpdateCallback',
            'callbackBody' => 'fkey=$(key)&userId=' . $customer->id
        );

        return Qiniu::getUpToken(config('constants.qiniu.user_bucket'), 'png', $policy);
    }

    /**
     * @param $customer_id
     * @param $price
     * @return mixed
     */
    public function updateCustomerAccount($customer_id, $price)
    {
        $customer = Customer::find($customer_id);
        $customer->balance = $customer->balance - $price;
        $customer->save();
    }

    /**
     * @param  $input
     * @return mixed
     */
    private function createCustomerStub($input)
    {
        $customer = new Customer();
        $customer->mini_program_open_id = isset($input['mini_program_open_id']) ? $input['mini_program_open_id'] : '';
        $customer->openid = isset($input['openid']) ? $input['openid'] : '';
        $customer->unionid = isset($input['unionid']) ? $input['unionid'] : '';
        $customer->platform = isset($input['platform']) ? $input['platform'] : 0;
        $customer->phone = isset($input['phone']) ? $input['phone'] : '';
        $customer->deposit = isset($input['deposit']) ? $input['deposit'] : 0;
        $customer->language = isset($input['language']) ? $input['language'] : '';
        $customer->nick_name = isset($input['nick_name']) ? $input['nick_name'] : '';
        $customer->gender = isset($input['gender']) ? $input['gender'] : 0;
        $customer->avatar_url = isset($input['avatar_url']) ? $input['avatar_url'] : 'default.png';
        $customer->status = isset($input['status']) ? $input['status'] : 1;
        return $customer;
    }
}
