<?php

namespace addons\webman\grid\Driver;

use addons\webman\filesystem\Filesystem;
use addons\webman\grid\Filter;
use ExAdmin\ui\component\grid\grid\Grid;
use ExAdmin\ui\contract\GridAbstract;
use ExAdmin\ui\response\Message;
use ExAdmin\ui\response\Response;
use ExAdmin\ui\support\Request;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletes;
use support\Db;
use Webman\RedisQueue\Client;


class Eloquent extends GridAbstract
{

    /**
     * @var Grid
     */
    protected $grid;
    /**
     * @var Builder
     */
    protected $builder;


    protected $tableField = [];

    public function initialize(Grid $grid, $repository)
    {
        parent::initialize($grid, $repository); // TODO: Change the autogenerated stub
        $this->builder = $this->repository->newQuery();
        if ($this->trashed() && $this->grid->isTrashed()) {
            $this->builder->onlyTrashed();
        }
        $this->setPk($this->repository->getKeyName());
    }

    /**
     * 是否有回收站
     * @return bool
     */
    public function trashed(): bool
    {
        return in_array(SoftDeletes::class, class_uses_recursive($this->repository));
    }


    /**
     * 恢复数据
     * @param array $ids 恢复id
     * @return Message
     */
    public function restore(array $ids): Message
    {
        $this->repository->whereIn($this->getPk(), $ids)->restore();
        return message_success(admin_trans('grid.restore_success'));
    }

    /**
     * 删除
     * @param array $ids 删除id
     * @param bool $all 是否删除全部
     * @return Message
     */
    public function delete(array $ids, bool $all = false): Message
    {
        $arg = $all ?: $ids;
        $result = $this->dispatchEvent('deling', [$arg]);
        if ($result instanceof Message) {
            return $result;
        }
        if ($this->grid->isTrashed()) {
            $result = $this->builder->when($all, function ($query) {
                $query->onlyTrashed();
            }, function ($query) use ($ids) {
                $query->whereIn($this->getPk(), $ids);
            })->forceDelete();
        } else {
            $result = $this->builder->when($all, function ($query) {
                $query->whereRaw('1=1');
            }, function ($query) use ($ids) {
                $query->whereIn($this->getPk(), $ids);
            })->delete();
        }
        $deletedResult = $this->dispatchEvent('deleted', [$arg]);
        if ($deletedResult instanceof Message) {
            return $deletedResult;
        }
        if ($result) {
            return message_success(admin_trans('grid.delete_success'));
        }
        return message_error(admin_trans('grid.delete_error'));
    }

    /**
     * 拖拽排序
     * @param int $id 排序id
     * @param int $sort 排序位置
     * @param string $field 字段
     * @return Message
     */
    public function dragSort($id, int $sort, string $field): Message
    {
        $result = $this->dispatchEvent('sorting', [$id, $sort, $field]);
        if ($result instanceof Message) {
            return $result;
        }
        $pk = $this->getPk();
        $selectRaw = "{$pk},(@rownum := @rownum+1),case when @rownum = {$sort} then @rownum := @rownum+1 else @rownum := @rownum end AS rownum";

        $sortSql = $this->builder->from(Db::raw("(SELECT @rownum := -1) r," . $this->repository->getTable()))
            ->selectRaw($selectRaw)
            ->reorder($field)
            ->where($pk, '<>', $id)
            ->toSql();
        $this->repository->where($pk, $id)->update([$field => $sort]);

        Db::connection($this->repository->getConnectionName())->statement("update {$this->repository->getTable()} inner join ({$sortSql}) a on a.{$pk}={$this->repository->getTable()}.{$pk} set `{$field}`=a.rownum", $this->builder->getBindings());

        return message_success(admin_trans('grid.sort_success'));
    }

    /**
     * 输入框排序
     * @param int $id 排序id
     * @param int $sort 排序位置
     * @param string $field 字段
     * @return Message
     */
    public function inputSort($id, int $sort, string $field): Message
    {
        $result = $this->dispatchEvent('sorting', [$id, $sort, $field]);
        if ($result instanceof Message) {
            return $result;
        }
        $this->repository->where($this->getPk(), $id)->update([$field => $sort]);
        return message_success(admin_trans('grid.sort_success'));
    }

    /**
     * 更新
     * @param array $ids 更新条件id集合
     * @param array $data 更新数据
     * @return Message
     */
    public function update(array $ids, array $data): Message
    {

        $result = $this->dispatchEvent('updateing', [$ids, $data]);
        if ($result instanceof Message) {
            return $result;
        }
        foreach ($ids as $id) {
            $model = $this->repository->find($id);
            foreach ($data as $field => $value) {
                $model->$field = $value;
            }
            $model->save();
        }
        $result = $this->dispatchEvent('updated', [$ids, $data]);
        if ($result instanceof Message) {
            return $result;
        }
        return message_success(admin_trans('grid.update_success'));
    }

    /**
     * 表格列触发排序
     * @param string $field 字段
     * @param string $sort 排序 asc desc
     * @return mixed
     */
    public function tableSort($field, $sort)
    {
        $this->builder->reorder($field, $sort);
    }

    /**
     * 筛选
     * @param array $rule
     */
    public function filter(array $rule)
    {
        new Filter($this->builder, $rule);
    }

    /**
     * 快捷搜索
     * @param string $keyword 关键词
     * @param string|array|\Closure $search 搜索设置
     * @return mixed
     */
    public function quickSearch($keyword, $search)
    {
        if ($keyword === '' || $keyword === null) {
            return;
        }
        if ($search instanceof \Closure) {
            $this->builder->where(function ($query) use ($search, $keyword) {
                call_user_func_array($search, [$query, $keyword]);
            });
        }
        if (is_string($search)) {
            $search = [$search];
        }
        if (is_array($search)) {
            $this->builder->where(function ($query) use ($search, $keyword) {
                foreach ($search as $field) {
                    $query->orWhere($field, "LIKE", "%$keyword%");
                }
            });
        } elseif (is_null($search)) {

            $this->builder->where(function ($query) use ($keyword) {
                $tableField[$this->repository->getTable()] = $this->getTableField();
                $table = $this->repository->getTable();
                $wheres = [];
                foreach ($this->grid->getColumns() as $column) {
                    $field = $column->attr('dataIndex');
                    $using = $column->using;
                    $fields = explode('.', $field);
                    if (count($fields) > 1) {
                        $field = array_pop($fields);
                        $model = $this->repository;
                        foreach ($fields as $relation) {
                            $model = $model->$relation()->getRelated();
                        }
                        if (!array_key_exists($model->getTable(), $tableField)) {
                            $tableField[$model->getTable()] = $this->getTableFieldInfo($model->getTable());
                        }
                        $relation = implode('.', $fields);
                        $relationTableField = $tableField[$model->getTable()];
                        $where = $this->buildOrWhere($field, $relationTableField, $using, $keyword);

                    } else {
                        $relation = $table;
                        $where = $this->buildOrWhere($field, $tableField[$table], $using, $keyword);
                    }
                    if (count($where) > 0) {
                        if (isset($wheres[$relation])) {
                            $wheres[$relation] = array_merge($wheres[$relation], $where);
                        } else {
                            $wheres[$relation] = $where;
                        }
                    }
                }
                foreach ($wheres as $relation => $where) {
                    if ($relation == $table) {
                        $this->addOrWhereBinding($query, $where);
                    } else {
                        $query->orWhereHas($relation, function ($q) use ($where) {
                            $q->where(function ($query) use ($where) {
                                $this->addOrWhereBinding($query, $where);
                            });
                        });
                    }
                }
            });

        }
    }

    protected function addOrWhereBinding($query, $wheres)
    {
        foreach ($wheres as $where) {
            $query->orWhere(...$where);
        }
    }

    protected function parseDateTime($type, $originValue)
    {
        if (in_array($type, ['datetime', 'timestamp', 'date'])) {
            $value = strtotime($originValue);
            if ($value === false) {
                return $value;
            }
            if ('date' == $type) {
                $value = date('Y-m-d', $value);
            } else {
                $value = date('Y-m-d H:i:s', $value);
            }
        } else {
            $value = $originValue;
        }
        return $value;
    }

    protected function buildOrWhere($field, $tableField, $using, $keyword)
    {
        $where = [];
        if (array_key_exists($field, $tableField)) {
            if (count($using) > 0) {
                foreach ($using as $key => $value) {
                    if (strpos($value, $keyword) !== false) {
                        $where[] = [$field, "=", $key];
                    }
                }
            } else {
                $value = $this->parseDateTime($tableField[$field]['type'], $keyword);
                if ($value !== false) {
                    if ($keyword == $value) {
                        $where[] = [$field, "LIKE", "%$keyword%"];
                    } else {
                        $where[] = [$field, "=", $value];
                    }
                }
            }
        }
        return $where;
    }

    /**
     * 预关联加载
     */
    protected function with()
    {
        $eagerLoad = $this->builder->getEagerLoads();
        $relations = [];
        foreach ($this->grid->getColumns() as $column) {
            $field = $column->attr('dataIndex');
            $fields = explode('.', $field);
            if (count($fields) > 1) {
                array_pop($fields);
                $relation = implode('.', $fields);
                if (method_exists($this->repository, $relation)) {
                    $relations[] = $relation;
                }
            }
        }
        $relations = array_merge($relations, $eagerLoad);
        $this->builder->setEagerLoads([]);
        $this->builder->with($relations);
    }

    /**
     * 数据源
     * @param int $page 第几页
     * @param int $size 分页大小
     * @param bool $hidePage 是否分页
     * @return mixed
     */
    public function data(int $page, int $size, bool $hidePage)
    {
        $totalBuilder = clone $this->builder;
        $totalBuilder = $totalBuilder->select(DB::raw('count(*) as total'))->get();
        if (count($totalBuilder) > 1) {
            $total = $totalBuilder->count();
        } else {
            $total = $totalBuilder->sum('total');
        }
        $this->setTotal($total);
        $this->with();
        if ($hidePage) {
            return $this->builder->get();
        } else {
            return $this->builder->forPage($page, $size)->get();
        }
    }

    /**
     * 返回总条数
     * @return int
     */
    public function total(): int
    {
        return $this->total;
    }

    /**
     * 导出数据
     * @param array $selectIds 导出选中id
     * @param array $columns 导出列
     * @param bool $all 是否导出全部
     * @return Response
     */
    public function export(array $selectIds, array $columns, bool $all): Response
    {
        $progressKey = Request::input('ex_admin_progress_key', uniqid());
        $export = $this->grid->getExport();
        $export->setProgressKey($progressKey);
        try {
            if (Request::input('ex_admin_queue')) {
                $request = (new Request())->getRequest();

                $data = Request::input() + ['ex_admin_progress_key' => $progressKey, 'ex_admin_request' => [
                        'method' => Request::getMethod(),
                        'server' =>  $request->server->all(),
                        'header' =>  $request->headers->all(),
                    ]];
                Client::send('ex-admin-grid-export', $data);
            } else {
                $arr = [];
                foreach ($columns as $column) {
                    if (isset($column['title']) && !empty($column['title'])) {
                        $arr[$column['dataIndex']] = $column['title'];
                    }
                    if (isset($column['children']) && !empty($column['children'])) {
                        foreach ($column['children'] as $children) {
                            if (isset($children['title']) && !empty($children['title'])) {
                                $arr[$children['dataIndex']] = $children['title'];
                            }
                        }
                    }
                }
                $columns = $arr;
                $this->builder->when(!$all, function (Builder $builder) use ($selectIds) {
                    $builder->whereKey($selectIds);
                });
                $count = $this->builder->count();
                $export->columns($columns)->count($count);
                $this->builder->chunk(500, function ($data) use ($export) {
                    $data = $this->grid->parseColumn($data, true);
                    $export->write($data, function ($export) {
                        $export->save(Filesystem::path(''));
                        return Filesystem::url($export->getFilename() . '.' . $export->getExtension());
                    });
                });
            }
        } catch (\Throwable $exception) {
            $export->exportError();
        }
        return $export->export();
    }

    protected function getTableFieldInfo($table)
    {
        $tableFields = Db::connection($this->repository->getConnectionName())->select('SHOW FULL COLUMNS FROM ' . $table);
        $fields = [];
        foreach ($tableFields as $tableField) {
            $tableField = json_decode(json_encode($tableField), true);
            $tableField = array_change_key_case($tableField);
            $fields[$tableField['field']] = $tableField;
        }
        return $fields;
    }

    protected function getTableField()
    {
        if (count($this->tableField) == 0) {
            $this->tableField = $this->getTableFieldInfo($this->repository->getTable());
        }
        return $this->tableField;
    }

    /**
     * @return Builder|mixed
     */
    public function model()
    {
        return $this->builder;
    }
}
