<?php

declare(strict_types=1);

namespace app\admin\controller\auth;

use ba\Random;
use Throwable;
use support\think\Db;
use support\validation\Validator;
use support\validation\ValidationException;
use app\common\controller\Backend;
use app\admin\model\Admin as AdminModel;
use support\Response;
use Webman\Http\Request;

class Admin extends Backend
{
    protected ?object $model = null;

    protected array|string $preExcludeFields = ['create_time', 'update_time', 'password', 'salt', 'login_failure', 'last_login_time', 'last_login_ip'];

    protected array|string $quickSearchField = ['username', 'nickname'];

    protected string|int|bool $dataLimit = 'parent';

    protected string $dataLimitField = 'id';

    protected function initController(Request $request): ?Response
    {
        $this->model = new AdminModel();
        return null;
    }

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

        if ($request->get('select') ?? $request->post('select')) {
            $selectRes = $this->select($request);
            if ($selectRes !== null) return $selectRes;
        }

        list($where, $alias, $limit, $order) = $this->queryBuilder();
        $query = $this->model
            ->withoutField('login_failure,password,salt')
            ->withJoin($this->withJoinTable, $this->withJoinType)
            ->alias($alias)
            ->where($where);

        // 仅返回“顶级角色组(pid=0)”下的管理员（用于远程下拉等场景）
        $topGroup = $request->get('top_group') ?? $request->post('top_group');
        if ($topGroup === '1' || $topGroup === 1 || $topGroup === true) {
            $query = $query
                ->join('admin_group_access aga', $alias['admin'] . '.id = aga.uid')
                ->join('admin_group ag', 'aga.group_id = ag.id')
                ->where('ag.pid', 0)
                ->distinct(true);
        }

        $res = $query
            ->order($order)
            ->paginate($limit);
        $items = $res->items();
        return $this->success('', [
            'list'   => $items,
            'total'  => $res->total(),
            'remark' => get_route_remark(),
        ]);
    }

    /**
     * 远程下拉（重写：支持 top_group=1 仅返回顶级组管理员）
     */
    protected function _select(): Response
    {
        if (empty($this->model)) {
            return $this->success('', [
                'list'  => [],
                'total' => 0,
            ]);
        }

        $pk = $this->model->getPk();

        $fields = [$pk];
        $quickSearchArr = is_array($this->quickSearchField) ? $this->quickSearchField : explode(',', (string) $this->quickSearchField);
        foreach ($quickSearchArr as $f) {
            $f = trim((string) $f);
            if ($f === '') continue;
            $f = str_contains($f, '.') ? substr($f, strrpos($f, '.') + 1) : $f;
            if ($f !== '' && !in_array($f, $fields, true)) {
                $fields[] = $f;
            }
        }

        list($where, $alias, $limit, $order) = $this->queryBuilder();
        $modelTable = strtolower($this->model->getTable());
        $mainAlias = ($alias[$modelTable] ?? $modelTable) . '.';

        // 联表时避免字段歧义：主表字段统一 select 为 "admin.xxx as xxx"
        $selectFields = [];
        foreach ($fields as $f) {
            $f = trim((string) $f);
            if ($f === '') continue;
            $selectFields[] = $mainAlias . $f . ' as ' . $f;
        }

        // 联表时避免排序字段歧义：无前缀的字段默认加主表前缀
        $qualifiedOrder = [];
        if (is_array($order)) {
            foreach ($order as $k => $v) {
                $k = (string) $k;
                $qualifiedOrder[str_contains($k, '.') ? $k : ($mainAlias . $k)] = $v;
            }
        }

        $query = $this->model
            ->field($selectFields)
            ->alias($alias)
            ->where($where);

        $topGroup = $this->request ? ($this->request->get('top_group') ?? $this->request->post('top_group')) : null;
        if ($topGroup === '1' || $topGroup === 1 || $topGroup === true) {
            $query = $query
                ->join('admin_group_access aga', $mainAlias . 'id = aga.uid')
                ->join('admin_group ag', 'aga.group_id = ag.id')
                ->where('ag.pid', 0)
                ->distinct(true);
        }

        $res = $query
            ->order($qualifiedOrder ?: $order)
            ->paginate($limit);

        return $this->success('', [
            'list'  => $res->items(),
            'total' => $res->total(),
        ]);
    }

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

        if ($request->method() === 'POST') {
            $data = $request->post();
            if (!$data) {
                return $this->error(__('Parameter %s can not be empty', ['']));
            }
            $data = $this->normalizeSingleGroup($data);
            if (!$this->hasSingleGroup($data['group_arr'] ?? null)) {
                return $this->error(__('Please select exactly one role group'));
            }

            if ($this->modelValidate) {
                try {
                    $rules = [
                        'username'  => 'required|string|regex:/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/|unique:admin,username',
                        'nickname'  => 'required|string',
                        'password'  => 'required|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/',
                        'email'     => 'email|unique:admin,email',
                        'mobile'    => 'regex:/^1[3-9]\d{9}$/|unique:admin,mobile',
                        'group_arr' => 'required|array',
                    ];
                    $messages = [
                        'username.regex' => __('Please input correct username'),
                        'password.regex' => __('Please input correct password'),
                    ];
                    Validator::make($data, $rules, $messages)->validate();
                } catch (ValidationException $e) {
                    return $this->error($e->getMessage());
                }
            }

            $passwd = $data['password'] ?? '';
            $data   = $this->excludeFields($data);
            $creatorChannelId = $this->getCreatorChannelId();
            $groupChannelId = $this->resolveChannelIdFromPrimaryGroup($data['group_arr'] ?? []);
            if (!$this->auth->isSuperAdmin()) {
                if ($creatorChannelId === null || $creatorChannelId === '') {
                    return $this->error(__('You have no permission'));
                }
                if ($groupChannelId === null || $groupChannelId === '') {
                    return $this->error(__('Selected role group is not bound to a channel'));
                }
                if ((string) $groupChannelId !== (string) $creatorChannelId) {
                    return $this->error(__('Selected role group channel does not match current account'));
                }
                $data['channel_id'] = $creatorChannelId;
                $data['parent_admin_id'] = $this->auth->id;
            } else {
                $data['channel_id'] = ($groupChannelId === null || $groupChannelId === '') ? null : $groupChannelId;
            }
            $data['invite_code'] = $this->generateUniqueInviteCode();
            $result = false;
            if (!empty($data['group_arr'])) {
                $authRes = $this->checkGroupAuth($data['group_arr']);
                if ($authRes !== null) return $authRes;
            }
            $this->model->startTrans();
            try {
                $result = $this->model->save($data);
                if (!empty($data['group_arr'])) {
                    $groupAccess = [];
                    foreach ($data['group_arr'] as $datum) {
                        $groupAccess[] = [
                            'uid'      => $this->model->id,
                            'group_id' => $datum,
                        ];
                    }
                    Db::name('admin_group_access')->insertAll($groupAccess);
                }
                $this->model->commit();

                if (!empty($passwd)) {
                    $this->model->resetPassword($this->model->id, $passwd);
                }
            } catch (Throwable $e) {
                $this->model->rollback();
                return $this->error($e->getMessage());
            }
            if ($result !== false) {
                return $this->success(__('Added successfully'));
            }
            return $this->error(__('No rows were added'));
        }

        return $this->error(__('Parameter error'));
    }

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

        $pk  = $this->model->getPk();
        $id  = $request->get($pk) ?? $request->post($pk);
        $row = $this->model->find($id);
        if (!$row) {
            return $this->error(__('Record not found'));
        }

        $dataLimitAdminIds = $this->getDataLimitAdminIds();
        if ($dataLimitAdminIds && !in_array($row[$this->dataLimitField], $dataLimitAdminIds)) {
            return $this->error(__('You have no permission'));
        }

        if ($request->method() === 'POST') {
            $data = $request->post();
            if (!$data) {
                return $this->error(__('Parameter %s can not be empty', ['']));
            }
            $data = $this->normalizeSingleGroup($data);
            if (!$this->hasSingleGroup($data['group_arr'] ?? null)) {
                return $this->error(__('Please select exactly one role group'));
            }

            $postedGroups = array_map('intval', $data['group_arr'] ?? []);
            $rowGroups = array_map('intval', $row->group_arr ?? []);
            sort($postedGroups);
            sort($rowGroups);

            // 当前管理员编辑自身时，不允许修改角色组
            if ((int)$this->auth->id === (int)$id) {
                $postedGroups = $data['group_arr'] ?? [];
                if (!is_array($postedGroups)) {
                    $postedGroups = [];
                }
                $originGroups = $row->group_arr ?? [];
                sort($postedGroups);
                sort($originGroups);
                if ($postedGroups !== $originGroups) {
                    return $this->error(__('You cannot modify your own management group!'));
                }
            }

            if ($this->modelValidate) {
                try {
                    $rules = [
                        'username'  => 'required|string|regex:/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/|unique:admin,username,' . $id,
                        'nickname'  => 'required|string',
                        'password'  => 'nullable|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/',
                        'email'     => 'email|unique:admin,email,' . $id,
                        'mobile'    => 'regex:/^1[3-9]\d{9}$/|unique:admin,mobile,' . $id,
                        'group_arr' => 'required|array',
                    ];
                    $messages = [
                        'username.regex' => __('Please input correct username'),
                        'password.regex' => __('Please input correct password'),
                    ];
                    Validator::make($data, $rules, $messages)->validate();
                } catch (ValidationException $e) {
                    return $this->error($e->getMessage());
                }
            }

            if ($this->auth->id == $data['id'] && ($data['status'] ?? '') == 'disable') {
                return $this->error(__('Please use another administrator account to disable the current account!'));
            }

            if (!empty($data['password'])) {
                $this->model->resetPassword($row->id, $data['password']);
            }

            $groupAccess = [];
            if (!empty($data['group_arr'])) {
                $checkGroups = [];
                $rowGroupArr = $row->group_arr ?? [];
                foreach ($data['group_arr'] as $datum) {
                    if (!in_array($datum, $rowGroupArr)) {
                        $checkGroups[] = $datum;
                    }
                    $groupAccess[] = [
                        'uid'      => $id,
                        'group_id' => $datum,
                    ];
                }
                $authRes = $this->checkGroupAuth($checkGroups);
                if ($authRes !== null) return $authRes;
            }

            $data   = $this->excludeFields($data);
            unset($data['invite_code']);
            $creatorChannelId = $this->getCreatorChannelId();
            $groupChannelId = $this->resolveChannelIdFromPrimaryGroup($data['group_arr'] ?? []);
            if (!$this->auth->isSuperAdmin()) {
                if ($creatorChannelId === null || $creatorChannelId === '') {
                    return $this->error(__('You have no permission'));
                }
                if ($groupChannelId === null || $groupChannelId === '') {
                    return $this->error(__('Selected role group is not bound to a channel'));
                }
                if ((string) $groupChannelId !== (string) $creatorChannelId) {
                    return $this->error(__('Selected role group channel does not match current account'));
                }
                $data['channel_id'] = $creatorChannelId;
            } else {
                $data['channel_id'] = ($groupChannelId === null || $groupChannelId === '') ? null : $groupChannelId;
            }
            $result = false;
            $this->model->startTrans();
            try {
                $result = $row->save($data);
                Db::name('admin_group_access')
                    ->where('uid', $id)
                    ->delete();
                if ($groupAccess) {
                    Db::name('admin_group_access')->insertAll($groupAccess);
                }
                $this->model->commit();
            } catch (Throwable $e) {
                $this->model->rollback();
                return $this->error($e->getMessage());
            }
            if ($result !== false) {
                return $this->success(__('Update successful'));
            }
            return $this->error(__('No rows updated'));
        }

        unset($row['salt'], $row['login_failure']);
        $row['password'] = '';
        return $this->success('', [
            'row' => $row
        ]);
    }

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

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

        $ids  = $request->get('ids') ?? $request->post('ids') ?? [];
        $ids  = is_array($ids) ? $ids : [];
        $where[] = [$this->model->getPk(), 'in', $ids];
        $data = $this->model->where($where)->select();

        $count = 0;
        $this->model->startTrans();
        try {
            foreach ($data as $v) {
                if ($v->id != $this->auth->id) {
                    $count += $v->delete();
                    Db::name('admin_group_access')
                        ->where('uid', $v['id'])
                        ->delete();
                }
            }
            $this->model->commit();
        } catch (Throwable $e) {
            $this->model->rollback();
            return $this->error($e->getMessage());
        }
        if ($count) {
            return $this->success(__('Deleted successfully'));
        }
        return $this->error(__('No rows were deleted'));
    }

    /**
     * 远程下拉（Admin 无自定义，走父类默认列表）
     */
    public function select(Request $request): Response
    {
        return parent::select($request);
    }

    private function checkGroupAuth(array $groups): ?Response
    {
        if ($this->auth->isSuperAdmin()) {
            return null;
        }
        $authGroups = $this->getManageableGroupIds();
        foreach ($groups as $group) {
            if (!in_array($group, $authGroups)) {
                return $this->error(__('You have no permission to add an administrator to this group!'));
            }
        }
        return null;
    }

    private function getManageableGroupIds(): array
    {
        if ($this->auth->isSuperAdmin()) {
            return Db::name('admin_group')->where('status', 1)->column('id');
        }
        $own = Db::name('admin_group_access')->where('uid', $this->auth->id)->column('group_id');
        $children = $this->auth->getAdminChildGroups();
        return array_values(array_unique(array_merge($own, $children)));
    }

    private function getCreatorChannelId(): mixed
    {
        $currentAdmin = Db::name('admin')
            ->field(['id', 'channel_id'])
            ->where('id', $this->auth->id)
            ->find();
        if ($currentAdmin && !empty($currentAdmin['channel_id'])) {
            return $currentAdmin['channel_id'];
        }

        return null;
    }

    /**
     * @param array<int|string> $groupIds
     */
    private function resolveChannelIdFromPrimaryGroup(array $groupIds): mixed
    {
        if ($groupIds === []) {
            return null;
        }
        $gid = $groupIds[0];
        if ($gid === null || $gid === '') {
            return null;
        }

        return Db::name('admin_group')->where('id', $gid)->value('channel_id');
    }

    private function generateUniqueInviteCode(): string
    {
        $tries = 0;
        do {
            $tries++;
            $code = strtoupper(substr(Random::uuid(), 0, 8));
            $exists = Db::name('admin')->where('invite_code', $code)->find();
        } while ($exists && $tries < 20);
        return $code;
    }

    private function normalizeSingleGroup(array $data): array
    {
        if (!array_key_exists('group_arr', $data)) {
            return $data;
        }
        $group = $data['group_arr'];
        if (is_array($group)) {
            $data['group_arr'] = array_values(array_filter($group, static function ($item) {
                return $item !== null && $item !== '';
            }));
            return $data;
        }
        if ($group === null || $group === '') {
            $data['group_arr'] = [];
            return $data;
        }
        $data['group_arr'] = [$group];
        return $data;
    }

    private function hasSingleGroup(mixed $groups): bool
    {
        return is_array($groups) && count($groups) === 1;
    }
}
