<?php

namespace common\libs\export;

use common\helpers\Cache as CacheHelper;
use common\helpers\Date;
use common\helpers\Form;
use common\helpers\http\RemoteUploadApi;
use common\helpers\Tool;
use common\libs\dataList\DataList;
use common\models\Export;
use Yii;
use yii\base\Exception;
use yii\base\StaticInstanceTrait;
use yii\base\Component;

class BaseExport extends Component {
    use StaticInstanceTrait;
    public $moduleId;
    /**
     * @var string the model class name. This property must be set.
     */
    public $modelClass;
    /**
     * @var
     */
    public $listModelClass;
    /**
     * @var int
     */
    public $pageSize = 100;
    /**
     * @var int
     */
    public $totalCount = 0;
    /**
     * @var int
     */
    public $offset = 0;
    /**
     * @var int
     */
    public $limit = 100;
    /**
     * csv文件名称
     * @var null
     */
    public $fileName = null;
    /**
     * 保存的基本路径
     * @var null
     */
    public $saveBasePath = null;
    /**
     * 保存路径
     * @var null
     */
    public $savePath = null;
    /**
     * 正在导出的id
     * 来源于exporting_file表
     * @var null
     */
    public $exportId = null;
    /**
     * 操作人的id
     * @var null
     */
    public $userId = null;
    /**
     * 任务id
     * @var null
     */
    public $taskId = null;
    /**
     * 是否完成
     * @var bool
     */
    public $completed = false;
    /**
     * @var array
     */
    public $params = [];
    /**
     * 文件句柄
     * @var null
     */
    private $_fp = null;
    /**
     * 是否上传远程
     * @var bool
     */
    public $isRemote = true;

    /**
     * @var DataList
     */
    private $_listModel;

    public static function factory($taskId) {
        return new self([
                'taskId' => $taskId
            ]
        );
    }

    /**
     *
     */
    public function init() {
        parent::init();
        $this->isRemote = \Yii::$app->params['isRemoteUpload'];
    }

    /**
     * 生成复杂的路径
     * @return string
     */
    public function pathCreator(...$params) {
        $hash = md5(implode('_', $params));
        $first = substr($hash, 0, 3);
        $second = substr($hash, 3, 2);
        $dir = $first . __DS__ . $second . __DS__;
        return $dir;
    }

    /**
     * 获取文件名
     * @return string|null
     * @throws \Exception
     */
    public function getFileName() {
        $this->fileName = date('YmdHis', Date::nowTime()) . random_int(1000, 9999) . '.csv';
        return $this->fileName;
    }

    /**
     * 获取保存的路径
     * @return array|null
     */
    public function getSavePath() {
        $this->savePath = $this->pathCreator(date('Ymd', Date::nowTime()));
        return trim($this->savePath, __DS__);
    }

    /**
     * @return bool|string|null
     */
    public function getSaveBasePath() {
        $this->saveBasePath = Yii::getAlias('@backendApi') . __DS__ . 'web' . __DS__ . 'upload' . __DS__ . \Yii::$app->params['excelLocalDir'];
        return trim($this->saveBasePath, __DS__);
    }

    /**
     * 创建目录(递归创建)
     *
     * @param $dir
     * @return string|false
     */
    public function mkdir($dir) {
        if (!is_dir($dir)) {
            $this->mkdir(dirname($dir));
            if (!mkdir($dir, 0777))
                return false;
            @exec('chown -R www:www /'.$dir);
            @exec('chmod -R 777 /'.$dir);
        }
        return $dir;
    }

    /**
     * @return array|mixed
     */
    public function getParams() {
        $this->params = CacheHelper::getAsyncParams($this->taskId);
        return $this->params;
    }

    /**
     * @return mixed|null
     */
    public function getUserId() {
        $this->userId = isset($this->params['userId']) ? $this->params['userId'] : null;
        return $this->userId;
    }

    /**
     * @return |null
     */
    public function getExportId() {
        $this->exportId = isset($this->params['exportId']) ? $this->params['exportId'] : null;
        return $this->userId;
    }

    /**
     * 生成
     * @return bool
     * @throws Exception
     * @throws \yii\base\InvalidConfigException
     * @throws \yii\httpclient\Exception
     */
    public function generate() {
        //Logger::info(date('Y-m-d H:i:s'), 'export');
        $this->getParams();
        if (!$this->params) {
            throw new Exception('无法获取需要的参数');
        }
        $path = $this->getSaveBasePath() . __DS__ . $this->getSavePath();
        $realFile = $this->mkdir($path) . __DS__ . $this->getFileName();
        $this->completed = false;
        $this->getExportId();
        $this->getUserId();
        $fileNameUpdated = false;
        $this->_fp = fopen($realFile, 'w');
        @exec('chown -R www:www /'.$realFile);
        @exec('chmod -R 777 /'.$realFile);
        // 获取列表数据及表头
        $this->_listModel = new $this->listModelClass();
        $this->_listModel->isExport = true;
        if(method_exists($this->_listModel, 'getExportHeaders')){
            if(method_exists($this->_listModel, 'exportPrepare')) {//导出数据提前设置参数
                $this->_listModel->exportPrepare(['condition' => $this->params['condition'], 'params' => $this->params['params'], 'others' => isset($this->params['others']) ? $this->params['others'] : [], 'page' => 0, 'pageSize' => $this->pageSize, 'userId' => $this->userId]);
            }
            $headers = $this->_listModel->getExportHeaders($this->userId);
            fputcsv($this->_fp, $headers);
            unset($headers);
            $this->_updateFirst($realFile, 1);
        } else {
            throw new Exception($this->listModelClass.'的getExportHeaders方法不存在');
        }
        $this->_loopWriteData($realFile, $fileNameUpdated);
        $this->complete();
        return true;
    }

    /**
     * 循环写入数据
     * @param $realFile
     * @param $fileNameUpdated
     * @param int $page
     * @param int $counter
     * @return string
     * @throws Exception
     */
    private function _loopWriteData($realFile, &$fileNameUpdated, $page = 0, $counter = 0){
        if(method_exists($this->_listModel, 'getList')){
            $list = $this->_listModel->getList(['condition'=>$this->params['condition'], 'params'=>isset($this->params['params']) ? $this->params['params'] : [], 'others'=>isset($this->params['others']) ? $this->params['others'] : [], 'page'=>$page, 'pageSize'=>$this->pageSize, 'userId'=>$this->userId]);
        } else {
            throw new Exception($this->listModelClass.'的getList方法不存在');
        }
        if($page >= $list['totalPages']){
            return 'finish';
        }
        if(!empty($list['list'])){
            foreach($list['list'] as $columnData){
                fputcsv($this->_fp, Tool::arrTextConvert($columnData));
                $counter++;

                if ($list['totalCount'] < $counter) {
//                    $counter = $list['totalCount'];
                    return 'finish';
                }
                $percent = intval(($counter / $list['totalCount']) * 100);
                $this->_updatePercent($percent);
//                if (!$fileNameUpdated) {
//                    $this->_updateFirst($realFile, $percent);
//                } else {
//                    $this->_updatePercent($percent);
//                }
//                $fileNameUpdated = true;
                unset($percent, $columnData);
            }
            $page++;
            unset($list);
            return $this->_loopWriteData($realFile, $fileNameUpdated, $page, $counter);
        }
        unset($list);
        return 'finish';
    }

    /**
     * 完成
     * @return bool
     * @throws Exception
     * @throws \yii\base\InvalidConfigException
     * @throws \yii\httpclient\Exception
     */
    public function complete() {
        $this->_updatePercent(100);
        if ($this->completed) {
            return true;
        }
        if ($this->_fp) {
            fclose($this->_fp);
        }
        // 把导出的文件上传至静态文件服务器
        if ($this->isRemote) {
            $realFile = $this->getSaveBasePath() . __DS__ . $this->getSavePath() . __DS__ . $this->getFileName();
            $remoteUploadApi = RemoteUploadApi::instance();
            if ($uploadResult = $remoteUploadApi->upload($realFile)) {
                Export::updateAll(['REMOTE_URL' => $uploadResult['url'], 'IS_EXPORTING' => 0, 'EXPORT_PERCENT' => 100, 'ENDED_AT' => Date::nowTime()], 'ID=:ID', [':ID' => $this->exportId]);
            } else {
                $this->_errorHandle();
                throw new Exception('文件远程上传失败');
            }
            // 删除本地临时文件
            unlink($realFile);
        } else {
            Export::updateAll(['IS_EXPORTING' => 0, 'EXPORT_PERCENT' => 100, 'ENDED_AT' => Date::nowTime()], 'ID=:ID', [':ID' => $this->exportId]);
        }

        \Yii::$app->swooleAsyncTimer->pushAsyncPercentToAdmin(100, ['MODEL' => 'EXPORT', 'ID' => $this->exportId, 'FIELD' => 'EXPORT_PERCENT']);
        CacheHelper::deleteAsyncParams($this->taskId);
        $this->completed = true;
    }

    /**
     * @param $message
     */
    private function _errorNotice($message) {
        Yii::$app->swooleAsyncTimer->pushAsyncResultToAdmin($this->userId, $message, false);
    }

    /**
     * 首次更新
     * @param $exportId
     * @param $realName
     */
    private function _updateFirst($realName, $percent) {
        $fileName = str_replace($this->getSaveBasePath() . __DS__, '', $realName);
        Export::updateAll([
            'FILE_NAME' => str_replace(__DS__, '/', $fileName),
            'UPDATED_AT' => Date::nowTime(),
            'EXPORT_PERCENT' => $percent,
        ], 'ID=:ID', [':ID' => $this->exportId]);
    }

    /**
     * 更新百分比
     * @param $exportId
     * @param $percent
     */
    private function _updatePercent($percent) {
        // 把数据写入数据库中
        Export::updateAll(['EXPORT_PERCENT' => $percent], 'ID=:ID', [':ID' => $this->exportId]);
        \Yii::$app->swooleAsyncTimer->pushAsyncPercentToAdmin($percent, ['MODEL' => 'EXPORT', 'ID' => $this->exportId, 'FIELD' => 'EXPORT_PERCENT']);
    }

    /**
     * 发生错误处理
     * @param $exportId
     */
    private function _errorHandle() {
        Export::updateAll(['IS_EXPORTING' => 0], 'ID=:ID', [':ID' => $this->exportId]);
    }

    /**
     * 导出逻辑
     * @param $filter
     * @param $listName
     * @param null $consoleRouter
     * @throws Exception
     */
    public function exportHandle($filter, $listName, $consoleRouter = null){
        $params = [
            'moduleId' => $this->moduleId,
            'listName' => $listName,
            'action' => $consoleRouter ? $consoleRouter : Yii::$app->controller->id.'/'.Yii::$app->controller->action->id, // 这里这么写，是因为调用的异步路由和同步的控制器和方法是一样的，所以，只要不传默认调和同步一样的异步方法
            'userId' => Yii::$app->user->id,
        ];
        $this->webToAsync($params,$filter);
    }

    /**
     * 页面到异步的请求
     * @param $params
     * @param array $extend
     * @return bool
     * @throws Exception
     */
    public function webToAsync($params, $extend = []) {
        $default = [
            'moduleId' => null,
            'listName' => null,
            'remark' => null,
            'action' => null,
            'userId' => null,
        ];
        $params = Tool::deepParse($params, $default);
        if (!$params['moduleId'] || !$params['listName'] || !$params['action']) {
            throw new Exception('请设置moduleId,listName和action');
        }

        // 把文件对应的相关资料存入数据库中
        $export = new Export();
        $export->EXPORT_NAME = $params['listName'] . '_' . date('y年m月d日H时i分s秒导出', Date::nowTime());
        $export->MODULE_NAME = $params['moduleId'];
        $export->REMARK = $params['remark'];
        $export->IS_EXPORTING = 1;
        $export->ADMIN_ID = \Yii::$app->user->id;
        $export->STARTED_AT = Date::nowTime();
        $export->CREATED_AT = Date::nowTime();
        $export->EXPORT_PERCENT = 0;
        $export->ENDED_AT = 0;
        $export->UPDATED_AT = 0;
        if (!$export->save()) {
            throw new Exception(Form::formatErrorsForApi($export->getErrors()));
        }
        $extend = array_merge($extend, [
            'exportId' => $export->ID,
            'userId' => $params['userId'],
        ]);
        // 异步处理添加任务
        $taskKey = \Yii::$app->swooleAsyncTimer->asyncHandle($params['action'], $extend);
        if($taskKey === false){
            // 删除刚刚添加的导出数据
            Export::deleteAll(['ID'=>$export->ID]);
            throw new Exception('导出失败，请求异步处理服务器失败');
        }
        return $taskKey;
    }
}