<?php

declare(strict_types=1);

namespace app\common\controller;

use Throwable;
use app\admin\library\Auth;
use app\common\library\token\TokenExpirationException;
use app\admin\library\traits\Backend as BackendTrait;
use support\Response;
use Webman\Http\Request as WebmanRequest;

/**
 * 后台控制器基类
 * 迁移 Auth 鉴权、权限校验、CRUD trait
 */
class Backend extends Api
{
    use BackendTrait;
    /**
     * 无需登录的方法
     */
    protected array $noNeedLogin = [];

    /**
     * 无需鉴权的方法
     */
    protected array $noNeedPermission = [];

    /**
     * 权限类实例
     * @var Auth|null
     */
    protected ?Auth $auth = null;

    /**
     * 模型类实例（子类设置）
     * @var object|null
     */
    protected ?object $model = null;

    /**
     * 新增/编辑时排除字段
     */
    protected array|string $preExcludeFields = [];

    /**
     * 权重字段
     */
    protected string $weighField = 'weigh';

    /**
     * 默认排序
     */
    protected string|array $defaultSortField = [];

    /**
     * 有序保证
     */
    protected string|array $orderGuarantee = [];

    /**
     * 快速搜索字段
     */
    protected string|array $quickSearchField = 'id';

    /**
     * 数据限制
     */
    protected bool|string|int $dataLimit = false;

    /**
     * 数据限制字段
     */
    protected string $dataLimitField = 'admin_id';

    /**
     * 数据限制开启时自动填充
     */
    protected bool $dataLimitFieldAutoFill = true;

    /**
     * 添加时自动填充 admin_id（当模型有 admin_id 字段但无数据权限限制时使用）
     */
    protected bool $autoFillAdminId = false;

    /**
     * 查看请求返回的主表字段
     */
    protected string|array $indexField = ['*'];

    /**
     * 是否开启模型验证
     */
    protected bool $modelValidate = true;

    /**
     * 是否开启模型场景验证
     */
    protected bool $modelSceneValidate = false;

    /**
     * 关联查询方法名
     */
    protected array $withJoinTable = [];

    /**
     * 关联查询 JOIN 方式
     */
    protected string $withJoinType = 'LEFT';

    /**
     * 输入过滤函数名（如 clean_xss，CRUD 含 editor 字段时自动设置）
     */
    protected string $inputFilter = '';

    /**
     * 后台初始化（需在控制器方法开头调用，在 initializeApi 之后）
     * @return Response|null 需直接返回时返回 Response，否则 null
     */
    public function initializeBackend(WebmanRequest $request): ?Response
    {
        $response = $this->initializeApi($request);
        if ($response !== null) {
            return $response;
        }

        // 调用子类 initialize（CRUD 生成的控制器在此设置 model）
        $this->initialize();

        $action = $this->getActionFromPath($request->path());
        $needLogin = !action_in_arr($this->noNeedLogin, $action);
        $needPermission = !action_in_arr($this->noNeedPermission, $action);

        try {
            $this->auth = Auth::instance();
            $token = get_auth_token(['ba', 'token'], $request);
            if ($token) {
                $this->auth->init($token);
            }
        } catch (TokenExpirationException) {
            if ($needLogin) {
                return $this->error(__('Token expiration'), [], 409);
            }
        }

        if ($needLogin) {
            if (!$this->auth->isLogin()) {
                return $this->error(__('Please login first'), [
                    'type' => Auth::NEED_LOGIN,
                ], 0, ['statusCode' => Auth::LOGIN_RESPONSE_CODE]);
            }
            if ($needPermission) {
                $controllerPath = $this->getControllerPath($request);
                $routePath = $controllerPath . '/' . $action;
                if (!$this->auth->check($routePath)) {
                    return $this->error(__('You have no permission'), [], 401);
                }
            }
        }

        event_trigger('backendInit', $this->auth);

        if (method_exists($this, 'initController')) {
            $initResp = $this->initController($request);
            if ($initResp !== null) {
                return $initResp;
            }
        }

        return null;
    }

    /**
     * 子类可覆盖，用于初始化 model 等（替代原 initialize）
     * @return Response|null 需直接返回时返回 Response，否则 null
     */
    protected function initController(WebmanRequest $request): ?Response
    {
        return null;
    }

    public function index(WebmanRequest $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) return $response;
        return $this->_index();
    }

    public function add(WebmanRequest $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) return $response;
        return $this->_add();
    }

    public function edit(WebmanRequest $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) return $response;
        return $this->_edit();
    }

    public function del(WebmanRequest $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) return $response;
        return $this->_del();
    }

    public function sortable(WebmanRequest $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) return $response;
        return $this->_sortable();
    }

    public function select(WebmanRequest $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) return $response;
        $this->_select();
        return $this->success();
    }

    /**
     * 查询参数构建器
     */
    public function queryBuilder(): array
    {
        if (empty($this->model) || !$this->request) {
            return [[], [], 10, []];
        }
        $pk = $this->model->getPk();
        $quickSearch = $this->request->get('quickSearch', '');
        $limit = $this->request->get('limit', 10);
        $limit = is_numeric($limit) ? intval($limit) : 10;
        $search = $this->request->get('search', []);
        $search = is_array($search) ? $search : [];
        $search = $this->filterSearchArray($search);
        $initKey = $this->request->get('initKey', $pk);
        $initValue = $this->request->get('initValue', '');
        $initOperator = $this->request->get('initOperator', 'in');

        $where = [];
        $modelTable = strtolower($this->model->getTable());
        $alias = [];
        $alias[$modelTable] = parse_name(basename(str_replace('\\', '/', get_class($this->model))));
        $mainTableAlias = $alias[$modelTable] . '.';

        if ($quickSearch) {
            $quickSearchArr = is_array($this->quickSearchField) ? $this->quickSearchField : explode(',', $this->quickSearchField);
            foreach ($quickSearchArr as $k => $v) {
                $quickSearchArr[$k] = str_contains($v, '.') ? $v : $mainTableAlias . $v;
            }
            $where[] = [implode('|', $quickSearchArr), 'LIKE', '%' . str_replace('%', '\%', $quickSearch) . '%'];
        }
        if ($initValue) {
            $where[] = [$initKey, $initOperator, $initValue];
            $limit = 999999;
        }

        foreach ($search as $field) {
            if (!is_array($field) || !isset($field['operator']) || !isset($field['field']) || !isset($field['val'])) {
                continue;
            }

            $field['operator'] = $this->getOperatorByAlias($field['operator']);

            if (str_contains($field['field'], '.')) {
                $fieldNameParts = explode('.', $field['field']);
                $fieldNamePartsLastKey = array_key_last($fieldNameParts);
                foreach ($fieldNameParts as $fieldNamePartsKey => $fieldNamePart) {
                    if ($fieldNamePartsKey !== $fieldNamePartsLastKey) {
                        $fieldNameParts[$fieldNamePartsKey] = parse_name($fieldNamePart);
                    }
                }
                $fieldName = implode('.', $fieldNameParts);
            } else {
                $fieldName = $mainTableAlias . $field['field'];
            }

            if (isset($field['render']) && $field['render'] == 'datetime') {
                if ($field['operator'] == 'RANGE') {
                    $datetimeArr = explode(',', $field['val']);
                    if (!isset($datetimeArr[1])) {
                        continue;
                    }
                    $datetimeArr = array_filter(array_map('strtotime', $datetimeArr));
                    $where[] = [$fieldName, str_replace('RANGE', 'BETWEEN', $field['operator']), $datetimeArr];
                    continue;
                }
                $where[] = [$fieldName, '=', strtotime($field['val'])];
                continue;
            }

            if ($field['operator'] == 'RANGE' || $field['operator'] == 'NOT RANGE') {
                $arr = explode(',', $field['val']);
                if (!isset($arr[0]) || $arr[0] === '') {
                    $operator = $field['operator'] == 'RANGE' ? '<=' : '>';
                    $arr = $arr[1];
                } elseif (!isset($arr[1]) || $arr[1] === '') {
                    $operator = $field['operator'] == 'RANGE' ? '>=' : '<';
                    $arr = $arr[0];
                } else {
                    $operator = str_replace('RANGE', 'BETWEEN', $field['operator']);
                }
                $where[] = [$fieldName, $operator, $arr];
                continue;
            }

            switch ($field['operator']) {
                case '=':
                case '<>':
                    $where[] = [$fieldName, $field['operator'], strval($field['val'])];
                    break;
                case 'LIKE':
                case 'NOT LIKE':
                    $where[] = [$fieldName, $field['operator'], '%' . str_replace('%', '\%', $field['val']) . '%'];
                    break;
                case '>':
                case '>=':
                case '<':
                case '<=':
                    $where[] = [$fieldName, $field['operator'], intval($field['val'])];
                    break;
                case 'FIND_IN_SET':
                    if (is_array($field['val'])) {
                        foreach ($field['val'] as $val) {
                            $where[] = [$fieldName, 'find in set', $val];
                        }
                    } else {
                        $where[] = [$fieldName, 'find in set', $field['val']];
                    }
                    break;
                case 'IN':
                case 'NOT IN':
                    $where[] = [$fieldName, $field['operator'], is_array($field['val']) ? $field['val'] : explode(',', $field['val'])];
                    break;
                case 'NULL':
                case 'NOT NULL':
                    $where[] = [$fieldName, strtolower($field['operator']), ''];
                    break;
            }
        }

        $dataLimitAdminIds = $this->getDataLimitAdminIds();
        if ($dataLimitAdminIds) {
            $where[] = [$mainTableAlias . $this->dataLimitField, 'in', $dataLimitAdminIds];
        }

        return [$where, $alias, $limit, $this->queryOrderBuilder()];
    }

    /**
     * 组合搜索条件过滤（子类可覆盖，例如去掉已删除的数据库字段）
     */
    protected function filterSearchArray(array $search): array
    {
        return $search;
    }

    /**
     * 查询的排序参数构建器
     */
    public function queryOrderBuilder(): array
    {
        $pk = $this->model->getPk();
        $order = $this->request ? $this->request->get('order') : null;
        $order = $order ?: $this->defaultSortField;

        if ($order && is_string($order)) {
            $orderParts = explode(',', $order);
            $orderField = trim($orderParts[0] ?? '');
            $orderDir = trim($orderParts[1] ?? 'asc');
            $order = $orderField !== '' ? [$orderField => $orderDir ?: 'asc'] : [];
        }
        if (!$this->orderGuarantee) {
            $this->orderGuarantee = [$pk => 'desc'];
        } elseif (is_string($this->orderGuarantee)) {
            $orderParts = explode(',', $this->orderGuarantee);
            $this->orderGuarantee = [$orderParts[0] => $orderParts[1] ?? 'asc'];
        }
        $orderGuaranteeKey = array_key_first($this->orderGuarantee);
        if (!is_array($order) || !array_key_exists($orderGuaranteeKey, $order)) {
            $order[$orderGuaranteeKey] = $this->orderGuarantee[$orderGuaranteeKey];
        }

        return $order;
    }

    /**
     * 数据权限控制 - 获取有权限访问的管理员 IDs
     */
    protected function getDataLimitAdminIds(): array
    {
        if (!$this->dataLimit || !$this->auth || $this->auth->isSuperAdmin()) {
            return [];
        }
        $adminIds = [];
        if ($this->dataLimit == 'parent') {
            $parentGroups = $this->auth->getAdminChildGroups();
            if ($parentGroups) {
                $adminIds = $this->auth->getGroupAdmins($parentGroups);
            }
        } elseif (is_numeric($this->dataLimit) && $this->dataLimit > 0) {
            $adminIds = $this->auth->getGroupAdmins([$this->dataLimit]);
            return in_array($this->auth->id, $adminIds) ? [] : [$this->auth->id];
        } elseif ($this->dataLimit == 'allAuth' || $this->dataLimit == 'allAuthAndOthers') {
            $allAuthGroups = $this->auth->getAllAuthGroups($this->dataLimit);
            $adminIds = $this->auth->getGroupAdmins($allAuthGroups);
        }
        $adminIds[] = $this->auth->id;
        return array_unique($adminIds);
    }

    /**
     * 从别名获取原始的逻辑运算符
     */
    protected function getOperatorByAlias(string $operator): string
    {
        $alias = [
            'ne'  => '<>',
            'eq'  => '=',
            'gt'  => '>',
            'egt' => '>=',
            'lt'  => '<',
            'elt' => '<=',
        ];
        return $alias[$operator] ?? $operator;
    }

    /**
     * 从 path 解析 action
     */
    protected function getActionFromPath(string $path): string
    {
        $parts = explode('/', trim($path, '/'));
        return $parts[array_key_last($parts)] ?? '';
    }

    /**
     * 从 Request 或路由解析控制器路径（如 auth/admin）
     * 优先从 $request->controller（Webman 路由匹配时设置）解析，否则从 path 解析
     */
    protected function getControllerPath(WebmanRequest $request): string
    {
        return get_controller_path($request);
    }
}
