<?php

declare(strict_types=1);

namespace ba;

use Throwable;
use support\think\Db;

/**
 * 权限规则类（Webman 迁移版）
 */
class Auth
{
    protected array $config = [
        'auth_group'        => 'admin_group',
        'auth_group_access' => 'admin_group_access',
        'auth_rule'         => 'admin_rule',
    ];

    protected array $children = [];

    public function __construct(array $config = [])
    {
        $this->config = array_merge($this->config, $config);
    }

    public function __get($name): mixed
    {
        return $this->config[$name] ?? null;
    }

    public function getMenus(int $uid): array
    {
        $this->children = [];
        $originAuthRules = $this->getOriginAuthRules($uid);
        foreach ($originAuthRules as $rule) {
            $this->children[$rule['pid']][] = $rule;
        }

        if (!isset($this->children[0])) {
            return [];
        }

        return $this->getChildren($this->children[0]);
    }

    private function getChildren(array $rules): array
    {
        foreach ($rules as $key => $rule) {
            if (array_key_exists($rule['id'], $this->children)) {
                $rules[$key]['children'] = $this->getChildren($this->children[$rule['id']]);
            }
        }
        return $rules;
    }

    public function check(string $name, int $uid, string $relation = 'or', string $mode = 'url'): bool
    {
        $ruleList = $this->getRuleList($uid);
        if (in_array('*', $ruleList)) {
            return true;
        }

        if ($name) {
            $name = strtolower($name);
            $name = str_contains($name, ',') ? explode(',', $name) : [$name];
        }
        $list = [];
        $requestParams = [];
        if ($mode === 'url' && function_exists('request')) {
            $req = request();
            $requestParams = $req ? array_merge($req->get(), $req->post()) : [];
            $requestParams = json_decode(strtolower(json_encode($requestParams, JSON_UNESCAPED_UNICODE)), true) ?? [];
        }

        foreach ($ruleList as $rule) {
            $query = preg_replace('/^.+\?/U', '', $rule);
            if ($mode === 'url' && $query !== $rule) {
                parse_str($query, $param);
                $intersect = array_intersect_assoc($requestParams, $param);
                $rule = preg_replace('/\?.*$/U', '', $rule);
                if (in_array($rule, $name) && $intersect == $param) {
                    $list[] = $rule;
                }
            } elseif (in_array($rule, $name)) {
                $list[] = $rule;
            }
        }
        if ($relation === 'or' && !empty($list)) {
            return true;
        }
        $diff = array_diff($name, $list);
        if ($relation === 'and' && empty($diff)) {
            return true;
        }

        return false;
    }

    public function getRuleList(int $uid): array
    {
        $ids = $this->getRuleIds($uid);
        if (empty($ids)) {
            return [];
        }

        $originAuthRules = $this->getOriginAuthRules($uid);
        $rules = [];
        if (in_array('*', $ids)) {
            $rules[] = '*';
        }
        foreach ($originAuthRules as $rule) {
            $rules[$rule['id']] = strtolower($rule['name']);
        }
        return array_unique($rules);
    }

    public function getOriginAuthRules(int $uid): array
    {
        $ids = $this->getRuleIds($uid);
        if (empty($ids)) {
            return [];
        }

        $where = [['status', '=', '1']];
        if (!in_array('*', $ids)) {
            $where[] = ['id', 'in', $ids];
        }
        $rules = Db::name($this->config['auth_rule'])
            ->withoutField(['remark', 'status', 'weigh', 'update_time', 'create_time'])
            ->where($where)
            ->order('weigh desc,id asc')
            ->select()
            ->toArray();
        foreach ($rules as $key => $rule) {
            if (!empty($rule['keepalive'])) {
                $rules[$key]['keepalive'] = $rule['name'];
            }
        }

        return $rules;
    }

    public function getRuleIds(int $uid): array
    {
        $groups = $this->getGroups($uid);
        $ids = [];
        foreach ($groups as $g) {
            $ids = array_merge($ids, explode(',', trim($g['rules'] ?? '', ',')));
        }
        return array_unique($ids);
    }

    public function getGroups(int $uid): array
    {
        $dbName = $this->config['auth_group_access'] ?: 'user';
        if ($this->config['auth_group_access']) {
            $userGroups = Db::name($dbName)
                ->alias('aga')
                ->join($this->config['auth_group'] . ' ag', 'aga.group_id = ag.id', 'LEFT')
                ->field('aga.uid,aga.group_id,ag.id,ag.pid,ag.name,ag.rules')
                ->where("aga.uid='$uid' and ag.status='1'")
                ->select()
                ->toArray();
        } else {
            $userGroups = Db::name($dbName)
                ->alias('u')
                ->join($this->config['auth_group'] . ' ag', 'u.group_id = ag.id', 'LEFT')
                ->field('u.id as uid,u.group_id,ag.id,ag.name,ag.rules')
                ->where("u.id='$uid' and ag.status='1'")
                ->select()
                ->toArray();
        }

        return $userGroups;
    }
}
