<?php
/**
 * Created by PhpStorm.
 * User: sunmoon<i@liming.me>
 * Date: 2019-07-31
 * Time: 09:22
 */

namespace common\libs\logging\operate;


use common\helpers\Form;
use common\helpers\Tool;
use common\libs\logging\operate\provider\BatchProvider;
use common\libs\logging\operate\provider\SingleProvider;
use common\models\User;
use SebastianBergmann\CodeCoverage\Report\PHP;
use yii\base\Component;
use yii\base\ErrorException;
use yii\base\Exception;
use yii\helpers\ArrayHelper;

abstract class AbstractOperate extends Component {
    /**
     * 保存日志的model
     * @var null
     */
    public $logModel = null;
    /**
     * 保存日志的form
     * @var null
     */
    public $logForm = null;
    /**
     * 用于查询保存存数据的model
     * @var
     */
    public $fetchClass = null;
    /**
     * @var array
     */
    public $attrLabels = [];
    /**
     * @var null
     */
    public $saveBeforeContent = null;
    /**
     * @var null
     */
    public $saveAfterContent = null;
    /**
     * @var array
     */
    public $columns = [];
    /**
     * @var null
     */
    public $data = null;
    /**
     * @var null
     */
    protected $_dataType = null;
    /**
     * @var array
     */
    public $params = [];
    /**
     * @var bool
     */
    protected $_isBatch = false;
    /**
     * @var string
     */
    protected $_batchField = 'ID';
    /**
     * @var string
     */
    protected $_optObjField = 'USER_ID';
    /**
     * @var null
     */
    protected $_errors = null;
    /**
     * Factory constructor.
     * @param array $config
     */
    public function __construct($config = []) {
        parent::__construct($config);
    }

    /**
     * @param array $format
     * @param array $params
     * @return array
     */
    public function paramsFormat(array $format, array $params = []){
        if(!$params){
            return $format;
        }
        return ArrayHelper::merge($format, $params);
    }

    /**
     * @return |null
     */
    public function getDataType(){
        return $this->_dataType;
    }

    /**
     * @param $value
     * @return $this
     */
    public function setDataType($value){
        $this->_dataType = $value;
        return $this;
    }
    /**
     * @return mixed
     */
    public function getIsBatch(){
        return $this->_isBatch;
    }

    /**
     * @param $value
     * @return $this
     */
    public function setIsBatch($value){
        $this->_isBatch = $value;
        return $this;
    }

    /**
     * @param $value
     * @return $this
     */
    public function setBatchField($value){
        $this->_batchField = $value;
        return $this;
    }

    /**
     * @param $value
     * @return $this
     */
    public function setOptObjField($value){
        $this->_optObjField = $value;
        return $this;
    }


    /**
     * @param $value
     * @return $this
     */
    public function setSaveBeforeContent($value){
        $this->saveBeforeContent = $value;
        return $this;
    }

    /**
     * @param $value
     * @return $this
     */
    public function setSaveAfterContent($value){
        $this->saveAfterContent = $value;
        return $this;
    }

    /**
     * 分发器
     * @param $method
     * @return array|null
     */
    public function dispatcher($method){
        $initParams = $this->paramsFormat([
            'data' => $this->data,
            'fetchClass' => $this->fetchClass,
            'dataType' => $this->_dataType,
            'attrLabels' => $this->attrLabels,
        ], $this->params);
        if($this->_isBatch){
            $entryClass = new BatchProvider($initParams);
        }else{
            $entryClass = new SingleProvider($initParams);
        }
        if(method_exists($entryClass, $method)){
            $entryClass->{$method}();
            return $entryClass->result;
        }
        return null;
    }

    /**
     * 更新之前
     * @param null $data
     * @param null $dataType
     * @param array $params
     * @return $this|bool
     */
    public function beforeUpdate($data = null, $dataType = null, array $params = []){
        if(!$data){
            return false;
        }
        $this->data = $data;
        $this->_dataType = $dataType;
        $this->params = $params;
        $this->saveBeforeContent = $this->dispatcher('beforeUpdate');
        return $this;
    }

    /**
     * @param $data
     * @param null $dataType
     * @param array $params
     * @return $this|bool
     */
    public function afterUpdate($data, $dataType = null, array $params = []){
        if(!$data){
            return false;
        }
        $this->data = $data;
        $this->_dataType = $dataType;
        $this->params = $params;
        $this->saveAfterContent = $this->dispatcher('afterUpdate');
        return $this;
    }

    /**
     * @param $data
     * @param null $dataType
     * @param array $params
     * @return $this|bool
     */
    public function beforeDelete($data, $dataType = null,  array $params = []){
        if(!$data){
            return false;
        }
        $this->data = $data;
        $this->params = $params;
        $this->_dataType = $dataType;
        $this->saveBeforeContent = $this->dispatcher('beforeDelete');
        return $this;
    }

    /**
     * @param $data
     * @param string $dataType
     * @param array $params
     * @return $this|bool
     */
    public function afterInsert($data, $dataType = 'ID', array $params = []){
        if(!$data || !$dataType){
            return false;
        }
        $this->data = $data;
        $this->params = $params;
        $this->_dataType = $dataType;

        $this->saveAfterContent = $this->dispatcher('afterInsert');
        return $this;
    }

    /**
     * @return $this
     */
    public function clean(){
        $this->columns = [];
        return $this;
    }

    /**
     * @param array $params
     * @return bool
     */
    public function save(array $params = []){
        $this->setColumn($params);

        if(!$this->columns){
            return false;
        }
//        $taskKey = \Yii::$app->swooleAsyncTimer->asyncHandle('log/save',  [
//            'logModel' => $this->logModel,
//            'logForm' => $this->logForm,
//            'isBatch' => $this->_isBatch,
//            'batchField' => $this->_batchField,
//            'optObjField' => $this->_optObjField,
//            'columns' => $this->columns,
//        ]);
//        if ($taskKey === false) {
//            $this->setErrors('异步请求日志服务失败');
//            return false;
//        }

        if($this->_isBatch){
            $modelClass = $this->logModel;
            // mongodb 的批量添加要特殊处理
            $db = $modelClass::getDb();
            $collectionName = $modelClass::collectionName();
            if(is_array($collectionName)){
                $collection = count($collectionName) > 1 ? $collectionName[1] : $collectionName[0];
            }else{
                $collection = $collectionName;
            }

            $batchList = [];
            $saveAfterContent = $this->columns[0]['save_after_content'] ?? [];
            $saveBeforeContent = $this->columns[0]['save_before_content'] ?? [];

            if( $saveBeforeContent && $saveAfterContent ) {
                $saveBeforeContentList = [];
                foreach ($saveBeforeContent as $key => $beforeItem) {
                    if (isset($beforeItem[$this->_batchField])) {
                        $saveBeforeContentList[$beforeItem[$this->_batchField]['value']] = $beforeItem;
                    } else {
                        $saveBeforeContentList[$key] = $beforeItem;
                    }
                }
                foreach ($saveAfterContent as $key => $afterItem) {
                    if (isset($afterItem[$this->_batchField])) {
                        if (!isset($saveBeforeContentList[$afterItem[$this->_batchField]['value']])) continue;

                        $beforeItem = $saveBeforeContentList[$afterItem[$this->_batchField]['value']];
                    } else {
                        if (!isset($saveBeforeContentList[$key])) continue;

                        $beforeItem = $saveBeforeContentList[$key];
                    }

                    $mergeData = [
                        'save_before_content' => $beforeItem,
                        'save_after_content' => $afterItem,
                    ];

                    //记录opt_obj_id 和 opt_obj_name @todo待优化
                    $this->_fixOptObjField($collection, $beforeItem);

                    $batchList[] = array_merge($this->columns[0], $mergeData);
                }
                unset($saveBeforeContentList);
            }else if( $saveBeforeContent && !$saveAfterContent ) {
                foreach ($saveBeforeContent as $key => $beforeItem) {
                    $mergeData = [
                        'save_before_content' => $beforeItem,
                        'save_after_content' => null,
                    ];
                    //记录opt_obj_id 和 opt_obj_name @todo待优化
                    $this->_fixOptObjField($collection, $beforeItem);
                    $batchList[] = array_merge($this->columns[0], $mergeData);
                }
            }else if( !$saveBeforeContent && $saveAfterContent ) {
                foreach ($saveAfterContent as $key => $afterItem) {
                    $mergeData = [
                        'save_before_content' => null,
                        'save_after_content' => $afterItem,
                    ];

                    //记录opt_obj_id 和 opt_obj_name @todo待优化
                    $this->_fixOptObjField($collection, $afterItem);

                    $batchList[] = array_merge($this->columns[0], $mergeData);
                }
            }else {
                $this->setErrors('日志批量写入错误');
                return false;
            }
            unset($saveBeforeContent, $saveAfterContent);

            if (!$batchList) {
                $this->setErrors('数据无效');
                return false;
            }
            if(!$db->createCommand()->batchInsert($collection, $batchList)){
                $this->setErrors('日志批量写入失败');
                return false;
            }

//            if(!$db->createCommand()->batchInsert($collection, $this->columns)){
//                $this->setErrors('日志批量写入失败');
//                return false;
//            }
            return true;
        }else {
            $modelFormClass = $this->logForm;
            $logForm = new $modelFormClass($this->columns[0]);
            if(!$logForm->add()){
                $this->setErrors($logForm->getErrors());
                return false;
            }
        }


        return true;
    }

    /**
     * 异步保存(异步控制器调用此方法)
     * @return bool
     */
    public function asyncSave() {
        try {
            if( !$this->columns || !isset($this->columns[0]) ) {
                throw new Exception('储存的数据错误');
            }
            if ($this->_isBatch) {
                $this->_batchInsert();
            } else {
                $modelFormClass = $this->logForm;
                $logForm = new $modelFormClass($this->columns[0]);
                if (!$logForm->add()) {
                    throw new Exception(Form::formatErrorsForApi($logForm->getErrors()));
                }
            }
            return true;
        }catch (\Exception $e) {
            $this->setErrors($e->getMessage());
            return false;
        }
    }

    /**
     * console里面记日志，直接保存，无需调用异步，因为本身就是异步
     * @param array $params
     * @return bool
     */
    public function saveByConsole(array $params = []){
        $this->setColumn($params);
        if(!$this->columns){
            return false;
        }

        try {
            if( !$this->columns || !isset($this->columns[0]) ) {
                throw new Exception('储存的数据错误');
            }

            if ($this->_isBatch) {
                $this->_batchInsert();
            } else {
                $modelFormClass = $this->logForm;
                $logForm = new $modelFormClass($this->columns[0]);
                if (!$logForm->add()) {
                    throw new Exception(Form::formatErrorsForApi($logForm->getErrors()));
                }
            }
        }catch (\Exception $e) {
            $this->setErrors($e->getMessage());
            return false;
        }

        return true;
    }

    /**
     * 批量写入
     * @return bool
     * @throws ErrorException
     * @throws \yii\base\InvalidConfigException
     */
    protected function _batchInsert() {
        $modelClass = $this->logModel;
        // mongodb 的批量添加要特殊处理
        $db = $modelClass::getDb();
        $collectionName = $modelClass::collectionName();
        if(is_array($collectionName)){
            $collection = count($collectionName) > 1 ? $collectionName[1] : $collectionName[0];
        }else{
            $collection = $collectionName;
        }

        $batchList = [];
        $saveAfterContent = $this->columns[0]['save_after_content'] ?? [];
        $saveBeforeContent = $this->columns[0]['save_before_content'] ?? [];

        if( $saveBeforeContent && $saveAfterContent ) {
            $saveBeforeContentList = [];
            foreach ($saveBeforeContent as $key => $beforeItem) {
                if (isset($beforeItem[$this->_batchField])) {
                    $saveBeforeContentList[$beforeItem[$this->_batchField]['value']] = $beforeItem;
                } else {
                    $saveBeforeContentList[$key] = $beforeItem;
                }
            }
            foreach ($saveAfterContent as $key => $afterItem) {
                if (isset($afterItem[$this->_batchField])) {
                    if (!isset($saveBeforeContentList[$afterItem[$this->_batchField]['value']])) continue;

                    $beforeItem = $saveBeforeContentList[$afterItem[$this->_batchField]['value']];
                } else {
                    if (!isset($saveBeforeContentList[$key])) continue;

                    $beforeItem = $saveBeforeContentList[$key];
                }

                $mergeData = [
                    'save_before_content' => $beforeItem,
                    'save_after_content' => $afterItem,
                ];

                //记录opt_obj_id 和 opt_obj_name @todo待优化
                $this->_fixOptObjField($collection, $beforeItem);

                $batchList[] = array_merge($this->columns[0], $mergeData);
            }
            unset($saveBeforeContentList);
        }else if( $saveBeforeContent && !$saveAfterContent ) {
            foreach ($saveBeforeContent as $key => $beforeItem) {
                $mergeData = [
                    'save_before_content' => $beforeItem,
                    'save_after_content' => null,
                ];
                //记录opt_obj_id 和 opt_obj_name @todo待优化
                $this->_fixOptObjField($collection, $beforeItem);
                $batchList[] = array_merge($this->columns[0], $mergeData);
            }
        }else if( !$saveBeforeContent && $saveAfterContent ) {
            foreach ($saveAfterContent as $key => $afterItem) {
                $mergeData = [
                    'save_before_content' => null,
                    'save_after_content' => $afterItem,
                ];

                //记录opt_obj_id 和 opt_obj_name @todo待优化
                $this->_fixOptObjField($collection, $afterItem);

                $batchList[] = array_merge($this->columns[0], $mergeData);
            }
        }else {
            throw new ErrorException('日志批量写入错误');
        }
        unset($saveBeforeContent, $saveAfterContent);

        if (!$batchList) {
            throw new ErrorException('数据无效' . var_export($this->columns, true));
        }
        if(!$db->createCommand()->batchInsert($collection, $batchList)){
            throw new ErrorException('日志批量写入失败');
        }
        return true;
    }

    /**
     * 修正批量时的补操作对象信息
     * @param $collection
     * @param $item
     * @return bool
     * @throws \yii\base\InvalidConfigException
     */
    private function _fixOptObjField($collection, $item) {
        if( $collection !== 'ar_bonus_admin_log') {
            return false;
        }
        if( !isset( $item[$this->_optObjField] ) ) {
            return false;
        }

        $this->columns[0]['opt_obj_id'] = $item[$this->_optObjField]['value'];

        if( isset( $item['USER_NAME'] ) ) {
            $this->columns[0]['opt_obj_name'] = $item['USER_NAME']['value'];
        }else {
            //@todo
            $userOne = User::find()->select(['USER_NAME'])->where('ID=:ID', ['ID'=>$item[$this->_optObjField]['value']])->asArray()->one();

            if( $userOne && isset($userOne['USER_NAME']) ) {
                $this->columns[0]['opt_obj_name'] = $userOne['USER_NAME'];
            }

        }

        return true;
    }

    /**
     * @param $error
     * @return $this
     */
    public function setErrors($error){
        $this->_errors = $error;
        return $this;
    }

    /**
     * @return array|null
     */
    public function getErrors(){
        return $this->_errors;
    }
}