<?php

namespace app\admin\controller\game;

use Throwable;
use app\common\controller\Backend;
use support\Response;
use Webman\Http\Request as WebmanRequest;

/**
 * 游戏配置
 */
class Config extends Backend
{
    /**
     * GameConfig模型对象
     * @var object|null
     * @phpstan-var \app\common\model\GameConfig|null
     */
    protected ?object $model = null;

    protected string|array $defaultSortField = 'group,desc';

    protected array $withJoinTable = ['channel'];

    protected array|string $preExcludeFields = ['create_time', 'update_time'];

    protected string|array $quickSearchField = ['ID'];

    /** 权重之和必须为 100 的配置标识 */
    private const WEIGHT_SUM_100_NAMES = ['default_tier_weight', 'default_kill_score_weight'];

    protected function initController(WebmanRequest $request): ?Response
    {
        $this->model = new \app\common\model\GameConfig();
        return null;
    }

    /**
     * @throws Throwable
     */
    protected function _add(): Response
    {
        if ($this->request && $this->request->method() === 'POST') {
            $data = $this->request->post();
            if (!$data) {
                return $this->error(__('Parameter %s can not be empty', ['']));
            }

            $data = $this->applyInputFilter($data);
            $data = $this->excludeFields($data);

            $err = $this->validateGameWeightPayload($data, null);
            if ($err !== null) {
                return $this->error($err);
            }

            if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
                $data[$this->dataLimitField] = $this->auth->id;
            }

            $result = false;
            $this->model->startTrans();
            try {
                if ($this->modelValidate) {
                    $validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
                    if (class_exists($validate)) {
                        $validate = new $validate();
                        if ($this->modelSceneValidate) {
                            $validate->scene('add');
                        }
                        $validate->check($data);
                    }
                }
                $result = $this->model->save($data);
                $this->model->commit();
            } 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'));
    }

    /**
     * @throws Throwable
     */
    protected function _edit(): Response
    {
        $pk = $this->model->getPk();
        $id = $this->request ? ($this->request->post($pk) ?? $this->request->get($pk)) : null;
        $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 ($this->request && $this->request->method() === 'POST') {
            $data = $this->request->post();
            if (!$data) {
                return $this->error(__('Parameter %s can not be empty', ['']));
            }

            $data = $this->applyInputFilter($data);
            $data = $this->excludeFields($data);

            if (!$this->auth->isSuperAdmin()) {
                $data['channel_id'] = $row['channel_id'];
                $data['group'] = $row['group'];
                $data['name'] = $row['name'];
                $data['title'] = $row['title'];
            }

            $err = $this->validateGameWeightPayload($data, $row['value'] ?? null);
            if ($err !== null) {
                return $this->error($err);
            }

            $result = false;
            $this->model->startTrans();
            try {
                if ($this->modelValidate) {
                    $validate = str_replace("\\model\\", "\\validate\\", get_class($this->model));
                    if (class_exists($validate)) {
                        $validate = new $validate();
                        if ($this->modelSceneValidate) {
                            $validate->scene('edit');
                        }
                        $data[$pk] = $row[$pk];
                        $validate->check($data);
                    }
                }
                $result = $row->save($data);
                $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'));
        }

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

    /**
     * game_weight：校验数值、键不可改（编辑）、和为 100（特定 name）
     *
     * @param array<string, mixed> $data
     */
    private function validateGameWeightPayload(array $data, ?string $originalValue): ?string
    {
        $group = $data['group'] ?? '';
        if ($group !== 'game_weight') {
            return null;
        }
        $name = $data['name'] ?? '';
        $value = $data['value'] ?? '';
        if (!is_string($value)) {
            return __('Parameter error');
        }

        $decoded = json_decode($value, true);
        if (!is_array($decoded)) {
            return __('Parameter error');
        }

        $keys = [];
        $numbers = [];
        foreach ($decoded as $item) {
            if (!is_array($item)) {
                return __('Parameter error');
            }
            foreach ($item as $k => $v) {
                $keys[] = $k;
                if (!is_numeric($v)) {
                    return __('Game config weight value must be numeric');
                }
                $num = (float) $v;
                if ($num > 100) {
                    return __('Game config weight each value must not exceed 100');
                }
                $numbers[] = $num;
            }
        }

        if (count($numbers) === 0) {
            return __('Parameter %s can not be empty', ['value']);
        }

        if ($originalValue !== null && $originalValue !== '') {
            $oldKeys = $this->extractGameWeightKeys($originalValue);
            if ($oldKeys !== $keys) {
                return __('Game config weight keys cannot be modified');
            }
        }

        if (in_array($name, self::WEIGHT_SUM_100_NAMES, true)) {
            $sum = array_sum($numbers);
            if (abs($sum - 100.0) > 0.000001) {
                return __('Game config weight sum must equal 100');
            }
        }

        return null;
    }

    /**
     * @return list<string>
     */
    private function extractGameWeightKeys(string $value): array
    {
        $decoded = json_decode($value, true);
        if (!is_array($decoded)) {
            return [];
        }
        $keys = [];
        foreach ($decoded as $item) {
            if (!is_array($item)) {
                continue;
            }
            foreach ($item as $k => $_) {
                $keys[] = $k;
            }
        }
        return $keys;
    }

    /**
     * 查看
     * @throws Throwable
     */
    protected function _index(): Response
    {
        // 如果是 select 则转发到 select 方法，若未重写该方法，其实还是继续执行 index
        if ($this->request && $this->request->get('select')) {
            return $this->select($this->request);
        }

        /**
         * 1. withJoin 不可使用 alias 方法设置表别名，别名将自动使用关联模型名称（小写下划线命名规则）
         * 2. 以下的别名设置了主表别名，同时便于拼接查询参数等
         * 3. paginate 数据集可使用链式操作 each(function($item, $key) {}) 遍历处理
         */
        list($where, $alias, $limit, $order) = $this->queryBuilder();
        $res = $this->model
            ->withJoin($this->withJoinTable, $this->withJoinType)
            ->with($this->withJoinTable)
            ->visible(['channel' => ['name']])
            ->alias($alias)
            ->where($where)
            ->order($order)
            ->paginate($limit);

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

    /**
     * 若需重写查看、编辑、删除等方法，请复制 @see \app\admin\library\traits\Backend 中对应方法至此进行重写
     */
}
