<?php

namespace app\common\controller;

use app\common\library\Auth;
use EasyWeChat\Factory;
use think\Config;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\Hook;
use think\Lang;
use think\Loader;
use think\Request;
use think\Response;
use think\Route;
use think\Validate;

/**
 * API控制器基类
 */
class Api {

	/**
	 * @var Request Request 实例
	 */
	protected $request;
	protected $param;

	/**
	 * @var bool 验证失败是否抛出异常
	 */
	protected $failException = false;

	/**
	 * @var bool 是否批量验证
	 */
	protected $batchValidate = false;

	/**
	 * @var array 前置操作方法列表
	 */
	protected $beforeActionList = [];

	/**
	 * 无需登录的方法,同时也就不需要鉴权了
	 * @var array
	 */
	protected $noNeedLogin = [];

	/**
	 * 无需鉴权的方法,但需要登录
	 * @var array
	 */
	protected $noNeedRight = [];

	/**
	 * 权限Auth
	 * @var Auth
	 */
	protected $auth = null;

	/**
	 * 默认响应输出类型,支持json/xml
	 * @var string
	 */
	protected $responseType = 'json';

	/**
	 * 构造方法
	 * @access public
	 *
	 * @param Request $request Request 对象
	 */
	public function __construct( Request $request = null ) {
		$this->request = is_null( $request ) ? Request::instance() : $request;
		if ( $this->request instanceof Request ) {

			$this->param = $this->request->param();
		}
		// 控制器初始化
		$this->_initialize();

		// 前置操作方法
		if ( $this->beforeActionList ) {
			foreach ( $this->beforeActionList as $method => $options ) {
				is_numeric( $method ) ?
					$this->beforeAction( $options ) :
					$this->beforeAction( $method, $options );
			}
		}
	}

	/**
	 * 初始化操作
	 * @access protected
	 */
	protected function _initialize() {
		//跨域请求检测
		check_cors_request();

		// 检测IP是否允许
		check_ip_allowed();

		//移除HTML标签
		$this->request->filter( 'trim,strip_tags,htmlspecialchars' );

		$this->auth = Auth::instance();

		$modulename     = $this->request->module();
		$controllername = Loader::parseName( $this->request->controller() );
		$actionname     = strtolower( $this->request->action() );

		// token
		$token = $this->request->server( 'HTTP_TOKEN', $this->request->request( 'token', \think\Cookie::get( 'token' ) ) );

		$path = str_replace( '.', '/', $controllername ) . '/' . $actionname;
		// 设置当前请求的URI
		$this->auth->setRequestUri( $path );
		// 检测是否需要验证登录
		if ( !$this->auth->match( $this->noNeedLogin ) ) {
			//初始化
			$this->auth->init( $token );
			//检测是否登录
			if ( !$this->auth->isLogin() ) {
				$this->error( __( 'Please login first' ), null, 401 );
			}
			// 判断是否需要验证权限
			if ( !$this->auth->match( $this->noNeedRight ) ) {
				// 判断控制器和方法判断是否有对应权限
				if ( !$this->auth->check( $path ) ) {
					$this->error( __( 'You have no permission' ), null, 403 );
				}
			}
		} else {
			// 如果有传递token才验证是否登录状态
			if ( $token ) {
				$this->auth->init( $token );
			}
		}

		$upload = \app\common\model\Config::upload();

		// 上传信息配置后
		Hook::listen( "upload_config_init", $upload );

		Config::set( 'upload', array_merge( Config::get( 'upload' ), $upload ) );

		// 加载当前控制器语言包
		$this->loadlang( $controllername );
	}

	/**
	 * 加载语言文件
	 *
	 * @param string $name
	 */
	protected function loadlang( $name ) {
		$name = Loader::parseName( $name );
		Lang::load( APP_PATH . $this->request->module() . '/lang/' . $this->request->langset() . '/' . str_replace( '.', '/', $name ) . '.php' );
	}

	/**
	 * 操作成功返回的数据
	 *
	 * @param string $msg 提示信息
	 * @param mixed $data 要返回的数据
	 * @param int $code 错误码,默认为1
	 * @param string $type 输出类型
	 * @param array $header 发送的 Header 信息
	 */
	protected function success( $msg = '', $data = null, $code = 1, $type = null, array $header = [] ) {
		$this->result( $msg, $data, $code, $type, $header );
	}

	/**
	 * 操作失败返回的数据
	 *
	 * @param string $msg 提示信息
	 * @param mixed $data 要返回的数据
	 * @param int $code 错误码,默认为0
	 * @param string $type 输出类型
	 * @param array $header 发送的 Header 信息
	 */
	protected function error( $msg = '', $data = null, $code = 0, $type = null, array $header = [] ) {
		$this->result( $msg, $data, $code, $type, $header );
	}

	/**
	 * 返回封装后的 API 数据到客户端
	 * @access protected
	 *
	 * @param mixed $msg 提示信息
	 * @param mixed $data 要返回的数据
	 * @param int $code 错误码,默认为0
	 * @param string $type 输出类型,支持json/xml/jsonp
	 * @param array $header 发送的 Header 信息
	 *
	 * @return void
	 * @throws HttpResponseException
	 */
	protected function result( $msg, $data = null, $code = 0, $type = null, array $header = [] ) {
		$result = [
			'code' => $code,
			'msg'  => $msg,
			'time' => Request::instance()->server( 'REQUEST_TIME' ),
			'data' => $data,
		];
		// 如果未设置类型则自动判断
		$type = $type ? $type : ( $this->request->param( config( 'var_jsonp_handler' ) ) ? 'jsonp' : $this->responseType );

		if ( isset( $header['statuscode'] ) ) {
			$code = $header['statuscode'];
			unset( $header['statuscode'] );
		} else {
			//未设置状态码,根据code值判断
			$code = $code >= 1000 || $code < 200 ? 200 : $code;
		}
		$response = Response::create( $result, $type, $code )->header( $header );
		throw new HttpResponseException( $response );
	}

	/**
	 * 前置操作
	 * @access protected
	 *
	 * @param string $method 前置操作方法名
	 * @param array $options 调用参数 ['only'=>[...]] 或者 ['except'=>[...]]
	 *
	 * @return void
	 */
	protected function beforeAction( $method, $options = [] ) {
		if ( isset( $options['only'] ) ) {
			if ( is_string( $options['only'] ) ) {
				$options['only'] = explode( ',', $options['only'] );
			}

			if ( !in_array( $this->request->action(), $options['only'] ) ) {
				return;
			}
		} elseif ( isset( $options['except'] ) ) {
			if ( is_string( $options['except'] ) ) {
				$options['except'] = explode( ',', $options['except'] );
			}

			if ( in_array( $this->request->action(), $options['except'] ) ) {
				return;
			}
		}

		call_user_func( [$this, $method] );
	}

	/**
	 * 设置验证失败后是否抛出异常
	 * @access protected
	 *
	 * @param bool $fail 是否抛出异常
	 *
	 * @return $this
	 */
	protected function validateFailException( $fail = true ) {
		$this->failException = $fail;

		return $this;
	}

	/**
	 * 验证数据
	 * @access protected
	 *
	 * @param array $data 数据
	 * @param string|array $validate 验证器名或者验证规则数组
	 * @param array $message 提示信息
	 * @param bool $batch 是否批量验证
	 * @param mixed $callback 回调方法(闭包)
	 *
	 * @return array|string|true
	 * @throws ValidateException
	 */
	protected function validate( $data, $validate, $message = [], $batch = false, $callback = null ) {
		if ( is_array( $validate ) ) {
			$v = Loader::validate();
			$v->rule( $validate );
		} else {
			// 支持场景
			if ( strpos( $validate, '.' ) ) {
				list( $validate, $scene ) = explode( '.', $validate );
			}

			$v = Loader::validate( $validate );

			!empty( $scene ) && $v->scene( $scene );
		}

		// 批量验证
		if ( $batch || $this->batchValidate ) {
			$v->batch( true );
		}
		// 设置错误信息
		if ( is_array( $message ) ) {
			$v->message( $message );
		}
		// 使用回调验证
		if ( $callback && is_callable( $callback ) ) {
			call_user_func_array( $callback, [$v, &$data] );
		}

		if ( !$v->check( $data ) ) {
			if ( $this->failException ) {
				throw new ValidateException( $v->getError() );
			}

			return $v->getError();
		}

		return true;
	}

	/**
	 * 刷新Token
	 */
	protected function token() {
		$token = $this->request->param( '__token__' );

		//验证Token
		if ( !Validate::make()->check( ['__token__' => $token], ['__token__' => 'require|token'] ) ) {
			$this->error( __( 'Token verification error' ), ['__token__' => $this->request->token()] );
		}

		//刷新Token
		$this->request->token();
	}

	/**
	 * 获取jssdk参数
	 */
	protected function jssdk_datas( $url ) {
		$url                     = urldecode( $url );
		$config                  = get_addon_config( 'third' );
		$wechat_config           = $config['wechat'];
		$wechat_config['secret'] = $wechat_config['app_secret'];
		$app                     = Factory::officialAccount( $wechat_config );
		$APIs                    = config( 'options.wechat_apis' );
		$app->jssdk->setUrl( $url );
		$data = $app->jssdk->buildConfig( $APIs, $debug = true, $beta = false, $json = true );
		return json_decode( $data, true );
	}

	protected function jssdk_data($url) {
		$APIs = [
			'checkJsAPI',
			'updateAppMessageShareData',
			'updateTimelineShareData',
			'onMenuShareTimeline',
			'onMenuShareAppMessage',
			'onMenuShareQQ',
			'onMenuShareWeibo',
			'onMenuShareQZone'
		];
		$url = urldecode($url);

		$config = get_addon_config( 'wechat' );
		$app    = Factory::officialAccount( $config );
		$app->jssdk->setUrl( $url );
		$data = $app->jssdk->setUrl($url)->buildConfig( $APIs, $debug = true, $beta = false, $json = true );
//		return json_decode( $data, true );
		return $data;
	}
}