<?php
// +----------------------------------------------------------------------
// | saiadmin [ saiadmin快速开发框架 ]
// +----------------------------------------------------------------------
// | Author: sai <1430792918@qq.com>
// +----------------------------------------------------------------------
namespace plugin\saiadmin\app\logic\system;

use app\dice\helper\AdminScopeHelper;
use plugin\saiadmin\app\cache\UserMenuCache;
use plugin\saiadmin\app\model\system\SystemRole;
use plugin\saiadmin\app\service\SystemRoleChannelService;
use plugin\saiadmin\basic\think\BaseLogic;
use plugin\saiadmin\exception\ApiException;
use support\think\Cache;
use support\think\Db;

/**
 * 角色逻辑层（按渠道 dept_id 隔离）
 */
class SystemRoleLogic extends BaseLogic
{
    protected string $orderField = 'level';

    protected string $orderType = 'desc';

    public function __construct()
    {
        $this->model = new SystemRole();
    }

    /**
     * 分页列表（按渠道过滤）
     */
    public function indexList(array $where, $requestDeptId = null): array
    {
        $query = $this->search($where);
        $this->applyDeptScope($query, $requestDeptId);
        $levelArr = array_column($this->adminInfo['roleList'] ?? [], 'level');
        if (!empty($levelArr)) {
            $maxLevel = max($levelArr);
            $query->where('level', '<', $maxLevel);
        }
        $query->where('id', '<>', SystemRoleChannelService::SUPER_ADMIN_ROLE_ID);
        return $this->getList($query);
    }

    public function add($data): bool
    {
        $data = $this->handleData($data);
        $deptId = AdminScopeHelper::normalizeRecordDeptId($data['dept_id'] ?? null);
        $data['dept_id'] = $deptId;
        $this->assertCodeUniqueInDept($data['code'] ?? '', $deptId, null);
        return $this->model->save($data);
    }

    public function edit($id, $data): bool
    {
        $model = $this->model->findOrEmpty($id);
        if ($model->isEmpty()) {
            throw new ApiException('Data not found');
        }
        $this->assertRoleWritable($model);
        $data = $this->handleData($data);
        $deptId = AdminScopeHelper::normalizeRecordDeptId($model->dept_id ?? $data['dept_id'] ?? null);
        $data['dept_id'] = $deptId;
        $this->assertCodeUniqueInDept($data['code'] ?? '', $deptId, (int) $id);
        return $model->save($data);
    }

    public function destroy($ids): bool
    {
        $levelArr = array_column($this->adminInfo['roleList'] ?? [], 'level');
        $maxLevel = !empty($levelArr) ? max($levelArr) : 100;

        $idList = is_array($ids) ? $ids : explode(',', (string) $ids);
        foreach ($idList as $roleId) {
            $roleId = (int) $roleId;
            if ($roleId === SystemRoleChannelService::SUPER_ADMIN_ROLE_ID) {
                throw new ApiException('Cannot delete super admin role');
            }
            $role = $this->model->find($roleId);
            if (!$role) {
                continue;
            }
            $this->assertRoleWritable($role);
            if ((int) ($role->level ?? 0) >= $maxLevel) {
                throw new ApiException('Cannot operate roles with higher level than current account');
            }
        }

        return $this->model->destroy($ids);
    }

    protected function handleData($data)
    {
        $levelArr = array_column($this->adminInfo['roleList'] ?? [], 'level');
        if (!empty($levelArr)) {
            $maxLevel = max($levelArr);
            if (($data['level'] ?? 0) >= $maxLevel) {
                throw new ApiException('Cannot operate roles with higher level than current account');
            }
        }
        return $data;
    }

    public function accessRole(array $where = [], $requestDeptId = null): array
    {
        $query = $this->search($where);
        $this->applyDeptScope($query, $requestDeptId);
        $levelArr = array_column($this->adminInfo['roleList'] ?? [], 'level');
        if (!empty($levelArr)) {
            $maxLevel = max($levelArr);
            $query->where('level', '<', $maxLevel);
        }
        $query->where('id', '<>', SystemRoleChannelService::SUPER_ADMIN_ROLE_ID);
        return $this->getAll($query);
    }

    public function getMenuIdsByRoleIds($ids): array
    {
        if (empty($ids)) {
            return [];
        }
        return $this->model->where('id', 'in', $ids)->with([
            'menus' => function ($query) {
                $query->where('status', 1)->order('sort', 'desc');
            }
        ])->select()->toArray();
    }

    public function getMenuByRole($id): array
    {
        $role = $this->model->findOrEmpty($id);
        if ($role->isEmpty()) {
            throw new ApiException('Data not found');
        }
        $this->assertRoleWritable($role);
        $menus = $role->menus ?: [];
        return [
            'id' => $id,
            'menus' => $menus
        ];
    }

    public function saveMenuPermission($id, $menu_ids): mixed
    {
        $role = $this->model->findOrEmpty($id);
        if ($role->isEmpty()) {
            throw new ApiException('Data not found');
        }
        $this->assertRoleWritable($role);

        return $this->transaction(function () use ($id, $menu_ids) {
            $role = $this->model->findOrEmpty($id);
            if ($role) {
                $role->menus()->detach();
                $data = array_map(function ($menu_id) use ($id) {
                    return ['menu_id' => $menu_id, 'role_id' => $id];
                }, $menu_ids);
                Db::name('sa_system_role_menu')->limit(100)->insertAll($data);
            }
            $cache = config('plugin.saiadmin.saithink.button_cache');
            $tag = $cache['role'] . $id;
            Cache::tag($tag)->clear();
            UserMenuCache::clearMenuCache();
            return true;
        });
    }

    /**
     * 解析并校验当前请求应操作的渠道 ID
     */
    public function resolveRequestDeptId($requestDeptId): int
    {
        if ((int) ($this->adminInfo['id'] ?? 0) === 1) {
            return AdminScopeHelper::resolveConfigDeptId($this->adminInfo, $requestDeptId);
        }
        $deptLogic = new SystemDeptLogic();
        $deptLogic->init($this->adminInfo);
        return $deptLogic->resolveAccessibleDeptId();
    }

    /**
     * 列表/下拉按渠道过滤
     */
    protected function applyDeptScope($query, $requestDeptId = null): void
    {
        if (!$this->tableHasDeptIdColumn()) {
            return;
        }
        if ((int) ($this->adminInfo['id'] ?? 0) === 1) {
            $deptId = AdminScopeHelper::resolveConfigDeptId($this->adminInfo, $requestDeptId);
            $query->where('dept_id', $deptId);
            return;
        }
        $deptLogic = new SystemDeptLogic();
        $deptLogic->init($this->adminInfo);
        $deptId = $deptLogic->resolveAccessibleDeptId();
        if ($deptId > 0) {
            $query->where('dept_id', $deptId);
        }
    }

    /**
     * 校验角色属于当前可操作渠道
     */
    public function assertRoleWritable($role): void
    {
        if (!$this->tableHasDeptIdColumn()) {
            return;
        }
        $roleDeptId = AdminScopeHelper::normalizeRecordDeptId($role->dept_id ?? null);
        if ((int) ($role->id ?? 0) === SystemRoleChannelService::SUPER_ADMIN_ROLE_ID) {
            throw new ApiException('Cannot operate super admin role');
        }
        if ((int) ($this->adminInfo['id'] ?? 0) === 1) {
            return;
        }
        $deptLogic = new SystemDeptLogic();
        $deptLogic->init($this->adminInfo);
        $scopeDeptId = $deptLogic->resolveAccessibleDeptId();
        if ($scopeDeptId > 0 && $roleDeptId !== $scopeDeptId) {
            throw new ApiException('No permission to operate this channel role');
        }
    }

    protected function assertCodeUniqueInDept(string $code, int $deptId, ?int $excludeId): void
    {
        if ($code === '') {
            return;
        }
        $query = SystemRole::where('code', $code)->where('dept_id', $deptId);
        if ($excludeId !== null && $excludeId > 0) {
            $query->where('id', '<>', $excludeId);
        }
        if ($query->count() > 0) {
            throw new ApiException('Role code already exists in this channel');
        }
    }

    protected function tableHasDeptIdColumn(): bool
    {
        try {
            $fields = Db::getFields((new SystemRole())->getTable());
            return isset($fields['dept_id']);
        } catch (\Throwable $e) {
            return false;
        }
    }
}
