<?php

declare(strict_types=1);

namespace app\admin\controller\auth;

use Throwable;
use support\think\Db;
use support\validation\Validator;
use support\validation\ValidationException;
use app\common\facade\Token;
use app\admin\model\AdminLog;
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', 'channel_id', 'totp_secret', 'totp_bind_time'];

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

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

    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();
        $res = $this->model
            ->withoutField('login_failure,password,salt')
            ->withJoin($this->withJoinTable ?? [], $this->withJoinType ?? 'LEFT')
            ->alias($alias)
            ->where($where)
            ->order($order)
            ->paginate($limit);

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

    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', ['']));
            }

            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);
            $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', ['']));
            }

            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;
            }

            Db::name('admin_group_access')
                ->where('uid', $id)
                ->delete();

            $data   = $this->excludeFields($data);
            $result = false;
            $this->model->startTrans();
            try {
                $result = $row->save($data);
                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 resetTotp(Request $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) return $response;

        if (!\app\common\library\AdminTotp::isEnabled()) {
            return $this->error(__('Google Authenticator is disabled'));
        }

        if ($request->method() !== 'POST') {
            return $this->error(__('Method not allowed'), [], 0, ['statusCode' => 405]);
        }

        if (!$this->auth->isSuperAdmin()) {
            return $this->error(__('You have no permission'));
        }

        $id = (int) ($request->post('id') ?? 0);
        if ($id <= 0) {
            return $this->error(__('Parameter error'));
        }
        if ($id === $this->auth->id) {
            return $this->error(__('Cannot reset your own authenticator, please modify in database'));
        }

        $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'));
        }

        AdminLog::instance($request)->setTitle(__('Reset Google Authenticator'));

        $row->save([
            'totp_secret' => '',
            'totp_bind_time' => null,
        ]);

        Token::clear(\app\admin\library\Auth::TOKEN_TYPE, $id);
        Token::clear(\app\admin\library\Auth::TOKEN_TYPE . '-refresh', $id);

        return $this->success(__('Google Authenticator reset successfully'));
    }

    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'));
    }

    /**
     * 远程下拉（返回管理员列表供 remoteSelect 使用）
     */
    public function select(Request $request): Response
    {
        $response = $this->initializeBackend($request);
        if ($response !== null) {
            return $response;
        }

        list($where, $alias, $limit, $order) = $this->queryBuilder();
        $res = $this->model
            ->withoutField('login_failure,password,salt')
            ->withJoin($this->withJoinTable ?? [], $this->withJoinType ?? 'LEFT')
            ->alias($alias)
            ->where($where)
            ->order($order)
            ->paginate($limit);

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

    /**
     * 去掉已废弃的渠道字段，避免组合搜索生成对不存在列的条件
     */
    protected function filterSearchArray(array $search): array
    {
        return array_values(array_filter($search, static function ($item) {
            if (!is_array($item) || !isset($item['field'])) {
                return true;
            }
            $f = (string) $item['field'];
            if ($f === 'channel_id' || str_ends_with($f, '.channel_id')) {
                return false;
            }
            if ($f === 'channel.name' || str_starts_with($f, 'channel.')) {
                return false;
            }

            return true;
        }));
    }

    public function queryOrderBuilder(): array
    {
        $order = parent::queryOrderBuilder();
        foreach (array_keys($order) as $key) {
            if ($key === 'channel_id' || (is_string($key) && str_contains($key, 'channel.'))) {
                unset($order[$key]);
            }
        }

        return $order;
    }

    private function checkGroupAuth(array $groups): ?Response
    {
        if ($this->auth->isSuperAdmin()) {
            return null;
        }
        $authGroups = $this->auth->getAllAuthGroups('allAuthAndOthers');
        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;
    }
}
