<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

namespace App\Services;

use App\Enums\AdminRoleStatus;
use App\Enums\AdminUserStatus;
use App\Models\DberpAdminPermission;
use App\Models\DberpAdminRole;
use App\Models\User;
use DBCore\Permission\Enums\PermissionEffect;
use DBCore\Permission\Models\RolePermission;
use DBCore\Permission\Models\UserRole;
use DomainException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

final class DberpAdminAccessService
{
    public function __construct(
        private readonly DberpPermissionRegistry $permissionRegistry,
        private readonly DberpPermissionService $permissionService,
        private readonly DberpAuditService $auditService,
    ) {}

    public function initializeSuperAdmin(): void
    {
        $this->syncPermissionCatalog();

        $role = DberpAdminRole::query()->updateOrCreate(
            ['name' => (string) config('dberp.admin.super_admin.role_name', 'super_admin')],
            [
                'display_name' => (string) config('dberp.admin.super_admin.role_display_name', '系统管理员'),
                'guard_name' => (string) config('dberp.admin.guard', 'web'),
                'description' => '系统预置超级管理员角色，默认拥有全部后台权限。',
                'sort_order' => 10,
                'status' => AdminRoleStatus::Enabled->value,
                'is_system' => true,
                'is_super_admin' => true,
            ],
        );

        $this->syncRolePermissions($role, $this->permissionRegistry->codes(), null, '初始化超级管理员角色权限', true);

        $loginAccount = (string) config('dberp.admin.super_admin.login_account', 'admin');
        $email = config('dberp.admin.super_admin.email');
        $email = filled($email) ? (string) $email : null;

        $user = User::query()->updateOrCreate(
            ['login_account' => $loginAccount],
            [
                'name' => (string) config('dberp.admin.super_admin.name', '系统管理员'),
                'nickname' => (string) config('dberp.admin.super_admin.nickname', '超管'),
                'email' => $email,
                'password' => (string) config('dberp.admin.super_admin.password', 'Admin@123456'),
                'status' => AdminUserStatus::Enabled->value,
            ],
        );

        $this->syncUserRoles($user, [$role->getKey()], null, '初始化超级管理员角色绑定', true);
    }

    /**
     * Web 安装向导：同步权限目录、创建或更新超级管理员角色（界面展示为「超级管理员」），并创建首任管理员。
     *
     * @param  array{name: string, login_account: string, password: string, email?: string|null, nickname?: string|null}  $administrator
     */
    public function bootstrapInstallationSuperAdministrator(array $administrator): User
    {
        $this->syncPermissionCatalog();

        $role = DberpAdminRole::query()->updateOrCreate(
            ['name' => (string) config('dberp.admin.super_admin.role_name', 'super_admin')],
            [
                'display_name' => '超级管理员',
                'guard_name' => (string) config('dberp.admin.guard', 'web'),
                'description' => '系统预置超级管理员角色，默认拥有全部后台权限。',
                'sort_order' => 10,
                'status' => AdminRoleStatus::Enabled->value,
                'is_system' => true,
                'is_super_admin' => true,
            ],
        );

        $this->syncRolePermissions($role, $this->permissionRegistry->codes(), null, '安装向导初始化超级管理员', true);

        $name = trim((string) ($administrator['name'] ?? ''));
        $nickname = filled($administrator['nickname'] ?? null)
            ? trim((string) $administrator['nickname'])
            : $name;

        return $this->createUser([
            'name' => $name,
            'nickname' => $nickname !== '' ? $nickname : $name,
            'login_account' => (string) ($administrator['login_account'] ?? ''),
            'email' => $administrator['email'] ?? null,
            'password' => (string) ($administrator['password'] ?? ''),
            'status' => AdminUserStatus::Enabled->value,
            'is_bootstrap_install_admin' => true,
        ], [(int) $role->getKey()], null, '安装向导创建首任超级管理员');
    }

    public function syncPermissionCatalog(): void
    {
        foreach ($this->permissionRegistry->records() as $code => $definition) {
            DberpAdminPermission::query()->updateOrCreate(
                ['name' => $code],
                collect($definition)
                    ->except(['default_allow_when_unconfigured', 'code', 'navigation_group', 'plugin_identifier', 'plugin_label', 'action', 'action_label'])
                    ->all(),
            );
        }
    }

    /**
     * @param  array{display_name: string, description?: string|null, sort_order?: int|null, status?: AdminRoleStatus|string|null}  $attributes
     * @param  list<string>  $permissionCodes
     */
    public function createRole(array $attributes, array $permissionCodes, ?User $actingUser = null, ?string $remark = null): DberpAdminRole
    {
        $status = $this->normalizeRoleStatus($attributes['status'] ?? null);
        $validPermissionCodes = $this->resolvePermissionCodes($permissionCodes);

        $this->assertCanCreateRole($actingUser, $validPermissionCodes !== []);

        return DB::transaction(function () use ($attributes, $status, $validPermissionCodes, $actingUser, $remark): DberpAdminRole {
            $role = DberpAdminRole::query()->create([
                'name' => $this->generateRoleName(),
                'display_name' => trim((string) $attributes['display_name']),
                'guard_name' => (string) config('dberp.admin.guard', 'web'),
                'description' => $this->normalizeNullableString($attributes['description'] ?? null),
                'sort_order' => $this->normalizeSortOrder($attributes['sort_order'] ?? null),
                'status' => $status->value,
                'is_system' => false,
                'is_super_admin' => false,
            ]);

            if ($validPermissionCodes !== []) {
                $this->syncRolePermissions($role, $validPermissionCodes, $actingUser, $remark, true);
            }

            $this->auditService->log('dberp.admin.role_created', $role, null, [
                'display_name' => $role->display_name,
                'description' => $role->description,
                'sort_order' => $role->sort_order,
                'status' => $status->label(),
                'permissions' => $role->permissions()->orderBy('sort_order')->pluck('display_name')->all(),
                'remark' => $remark,
            ]);

            return $role;
        });
    }

    /**
     * @param  array{name: string, nickname?: string|null, login_account: string, email?: string|null, password: string, status?: AdminUserStatus|string|null, is_bootstrap_install_admin?: bool}  $attributes
     * @param  list<int>  $roleIds
     */
    public function createUser(array $attributes, array $roleIds, ?User $actingUser = null, ?string $remark = null): User
    {
        $roles = $this->resolveRoles($roleIds);

        $this->assertCanCreateUser($actingUser, $roles);

        return DB::transaction(function () use ($attributes, $roles, $actingUser, $remark): User {
            $status = $this->normalizeUserStatus($attributes['status'] ?? null);
            $loginAccount = $this->normalizeLoginAccount((string) ($attributes['login_account'] ?? ''));
            $this->assertLoginAccountUnique($loginAccount, null);
            $email = $this->normalizeOptionalEmail($attributes['email'] ?? null);

            $user = User::query()->create([
                'name' => (string) $attributes['name'],
                'nickname' => filled($attributes['nickname'] ?? null) ? (string) $attributes['nickname'] : null,
                'login_account' => $loginAccount,
                'email' => $email,
                'password' => (string) $attributes['password'],
                'status' => $status->value,
                'is_bootstrap_install_admin' => (bool) ($attributes['is_bootstrap_install_admin'] ?? false),
            ]);

            $this->syncUserRoles(
                $user,
                $roles->pluck('id')->map(fn (mixed $roleId): int => (int) $roleId)->all(),
                $actingUser,
                $remark,
                true,
                (bool) ($attributes['is_bootstrap_install_admin'] ?? false),
            );

            $this->auditService->log('dberp.admin.user_created', $user, null, [
                'name' => $user->name,
                'nickname' => $user->nickname,
                'login_account' => $user->login_account,
                'email' => $user->email,
                'status' => $status->label(),
                'roles' => $roles->sortBy('sort_order')->pluck('display_name')->all(),
                'remark' => $remark,
            ]);

            return $user;
        });
    }

    /**
     * @param  array{name: string, nickname?: string|null, login_account: string, email?: string|null, password?: string|null, status?: AdminUserStatus|string|null}  $attributes
     * @param  list<int>  $roleIds
     */
    public function updateUser(User $targetUser, array $attributes, array $roleIds, ?User $actingUser = null, ?string $remark = null): User
    {
        $roles = $this->resolveRoles($roleIds);
        $status = $this->normalizeUserStatus($attributes['status'] ?? $targetUser->status ?? null);

        $this->assertCanUpdateUser($actingUser, $targetUser, $roles, $status);
        $this->guardAgainstRemovingLastSuperAdmin(
            $targetUser,
            $roles->pluck('id')->map(fn (mixed $roleId): int => (int) $roleId)->all(),
            $status,
        );
        $this->guardAgainstSelfLockout(
            $actingUser,
            $targetUser,
            $roles->pluck('id')->map(fn (mixed $roleId): int => (int) $roleId)->all(),
            $status,
        );

        return DB::transaction(function () use ($targetUser, $attributes, $roles, $status, $actingUser, $remark): User {
            $loginAccount = $this->normalizeLoginAccount((string) ($attributes['login_account'] ?? $targetUser->login_account));
            $this->assertLoginAccountUnique($loginAccount, $targetUser->getKey());
            $email = $this->normalizeOptionalEmail($attributes['email'] ?? null);

            $oldValues = [
                'name' => $targetUser->name,
                'nickname' => $targetUser->nickname,
                'login_account' => $targetUser->login_account,
                'email' => $targetUser->email,
                'status' => $targetUser->status instanceof AdminUserStatus ? $targetUser->status->label() : (string) $targetUser->status,
            ];

            $targetUser->forceFill([
                'name' => (string) $attributes['name'],
                'nickname' => filled($attributes['nickname'] ?? null) ? (string) $attributes['nickname'] : null,
                'login_account' => $loginAccount,
                'email' => $email,
                'status' => $status->value,
            ]);

            if (filled($attributes['password'] ?? null)) {
                $targetUser->password = (string) $attributes['password'];
            }

            $targetUser->save();

            $currentRoleIds = $targetUser->adminRoles()
                ->pluck('core_roles.id')
                ->map(fn (mixed $roleId): int => (int) $roleId)
                ->sort()
                ->values()
                ->all();
            $nextRoleIds = $roles->pluck('id')
                ->map(fn (mixed $roleId): int => (int) $roleId)
                ->sort()
                ->values()
                ->all();

            if ($currentRoleIds !== $nextRoleIds) {
                $this->syncUserRoles($targetUser, $nextRoleIds, $actingUser, $remark, true, false);
            }

            $this->auditService->log('dberp.admin.user_updated', $targetUser, $oldValues, [
                'name' => $targetUser->name,
                'nickname' => $targetUser->nickname,
                'login_account' => $targetUser->login_account,
                'email' => $targetUser->email,
                'status' => $status->label(),
                'remark' => $remark,
            ]);

            return $targetUser->refresh();
        });
    }

    public function resetUserPassword(User $targetUser, string $password, ?User $actingUser = null, ?string $remark = null): void
    {
        $this->assertCanResetUserPassword($actingUser, $targetUser);

        $targetUser->forceFill([
            'password' => $password,
        ])->save();

        $this->auditService->log('dberp.admin.user_password_reset', $targetUser, null, [
            'remark' => $remark,
        ]);
    }

    public function deleteUser(User $targetUser, ?User $actingUser = null, ?string $remark = null): void
    {
        $this->assertCanDeleteUser($actingUser, $targetUser);

        if ($targetUser->isBootstrapInstallAdmin()) {
            $this->throwProtectedOperation(
                'dberp.admin.user_delete_blocked',
                (string) __('dberp.errors.bootstrap_admin_cannot_delete'),
                $targetUser,
                ['remark' => $remark],
            );
        }

        if ($actingUser instanceof User && $actingUser->is($targetUser)) {
            $this->throwProtectedOperation(
                'dberp.admin.user_delete_blocked',
                (string) __('dberp.errors.admin_user_delete_self_blocked'),
                $targetUser,
                ['remark' => $remark],
            );
        }

        if ($targetUser->isAdminEnabled() && $targetUser->isSuperAdmin()) {
            $this->guardAgainstRemovingLastSuperAdmin($targetUser, [], AdminUserStatus::Disabled);
        }

        DB::transaction(function () use ($targetUser, $remark): void {
            $oldValues = [
                'name' => $targetUser->name,
                'nickname' => $targetUser->nickname,
                'login_account' => $targetUser->login_account,
                'email' => $targetUser->email,
                'status' => $targetUser->status instanceof AdminUserStatus ? $targetUser->status->label() : (string) $targetUser->status,
                'roles' => $targetUser->adminRoles()->orderBy('sort_order')->pluck('display_name')->all(),
            ];

            UserRole::query()
                ->where('user_id', $targetUser->getKey())
                ->where('user_type', User::class)
                ->where('guard_name', (string) config('dberp.admin.guard', 'web'))
                ->delete();

            $targetUser->delete();

            $this->auditService->log('dberp.admin.user_deleted', null, $oldValues, [
                'remark' => $remark,
            ]);
        });
    }

    /**
     * @param  array{display_name: string, description?: string|null, sort_order?: int|null, status?: AdminRoleStatus|string|null}  $attributes
     * @param  list<string>  $permissionCodes
     */
    public function updateRole(DberpAdminRole $role, array $attributes, array $permissionCodes, ?User $actingUser = null, ?string $remark = null): DberpAdminRole
    {
        $status = $this->normalizeRoleStatus($attributes['status'] ?? $role->status ?? null);
        $validPermissionCodes = $this->resolvePermissionCodes($permissionCodes);
        $normalizedDescription = $this->normalizeNullableString($attributes['description'] ?? null);
        $normalizedSortOrder = $this->normalizeSortOrder($attributes['sort_order'] ?? $role->sort_order ?? null);
        $normalizedDisplayName = trim((string) $attributes['display_name']);

        $currentStatus = $role->status instanceof AdminRoleStatus
            ? $role->status
            : AdminRoleStatus::from((string) $role->status);

        $storedPermissionCodes = $role->permissions()
            ->orderBy('sort_order')
            ->pluck('core_permissions.name')
            ->all();

        $registryPermissionCodes = $this->permissionRegistry->codes();

        $nextPermissionCodes = $role->is_super_admin ? $registryPermissionCodes : $validPermissionCodes;

        $isBasicChanged = $normalizedDisplayName !== (string) $role->display_name
            || $normalizedDescription !== $this->normalizeNullableString($role->description)
            || $normalizedSortOrder !== (int) $role->sort_order;
        $isStatusChanged = $status !== $currentStatus;
        $isPermissionsChanged = collect($storedPermissionCodes)->sort()->values()->all() !== collect($nextPermissionCodes)->sort()->values()->all();

        $this->assertCanUpdateRole($actingUser, $role, $isBasicChanged, $isPermissionsChanged, $isStatusChanged);

        if ($isStatusChanged && $role->isSystemPreset() && $status === AdminRoleStatus::Disabled) {
            $this->throwProtectedOperation(
                'dberp.admin.role_status_blocked',
                (string) __('dberp.errors.admin_role_system_cannot_disable'),
                $role,
                ['remark' => $remark],
            );
        }

        if ($isStatusChanged) {
            $this->guardAgainstDisablingLastSuperAdminRole($role, $status);
        }

        return DB::transaction(function () use ($role, $normalizedDisplayName, $normalizedDescription, $normalizedSortOrder, $status, $validPermissionCodes, $isBasicChanged, $isPermissionsChanged, $isStatusChanged, $currentStatus, $actingUser, $remark): DberpAdminRole {
            if ($isBasicChanged) {
                $oldValues = [
                    'display_name' => $role->display_name,
                    'description' => $role->description,
                    'sort_order' => $role->sort_order,
                ];

                $role->forceFill([
                    'display_name' => $normalizedDisplayName,
                    'description' => $normalizedDescription,
                    'sort_order' => $normalizedSortOrder,
                ])->save();

                $this->auditService->log('dberp.admin.role_updated', $role, $oldValues, [
                    'display_name' => $role->display_name,
                    'description' => $role->description,
                    'sort_order' => $role->sort_order,
                    'remark' => $remark,
                ]);
            }

            if ($isStatusChanged) {
                $role->forceFill([
                    'status' => $status->value,
                ])->save();

                $this->auditService->log('dberp.admin.role_status_updated', $role, [
                    'status' => $currentStatus->label(),
                ], [
                    'status' => $status->label(),
                    'remark' => $remark,
                ]);
            }

            if ($isPermissionsChanged) {
                $this->syncRolePermissions($role, $validPermissionCodes, $actingUser, $remark, true);
            }

            return $role->refresh();
        });
    }

    /**
     * @param  array{display_name: string, description?: string|null, sort_order?: int|null, status?: AdminRoleStatus|string|null}  $attributes
     */
    public function copyRole(DberpAdminRole $sourceRole, array $attributes, ?User $actingUser = null, ?string $remark = null): DberpAdminRole
    {
        $this->assertCanCopyRole($actingUser, $sourceRole);

        $permissionCodes = $sourceRole->permissions()
            ->orderBy('sort_order')
            ->pluck('core_permissions.name')
            ->all();

        return DB::transaction(function () use ($sourceRole, $attributes, $permissionCodes, $actingUser, $remark): DberpAdminRole {
            $role = $this->createRole($attributes, $permissionCodes, $actingUser, $remark);

            $this->auditService->log('dberp.admin.role_copied', $role, null, [
                'source_role' => $sourceRole->display_name,
                'display_name' => $role->display_name,
                'remark' => $remark,
            ]);

            return $role;
        });
    }

    /**
     * @param  list<int>  $roleIds
     */
    public function syncUserRoles(User $targetUser, array $roleIds, ?User $actingUser = null, ?string $remark = null, bool $skipAuthorization = false, bool $allowBootstrapInstallRoleProvisioning = false): void
    {
        $roles = DberpAdminRole::query()
            ->whereIn('id', array_values(array_unique($roleIds)))
            ->get();

        $nextSorted = collect($roleIds)
            ->map(fn (mixed $roleId): int => (int) $roleId)
            ->unique()
            ->sort()
            ->values()
            ->all();

        $currentSorted = $targetUser->adminRoles()
            ->pluck('core_roles.id')
            ->map(fn (mixed $roleId): int => (int) $roleId)
            ->sort()
            ->values()
            ->all();

        if ($currentSorted === $nextSorted) {
            return;
        }

        if ($targetUser->isBootstrapInstallAdmin() && ! $allowBootstrapInstallRoleProvisioning) {
            $this->throwProtectedOperation(
                'dberp.admin.user_roles_blocked',
                (string) __('dberp.errors.bootstrap_admin_roles_locked'),
                $targetUser,
                ['remark' => $remark],
            );
        }

        if (! $skipAuthorization) {
            $this->assertCanManageUserRoles($actingUser, $roles);
            $this->guardAgainstRemovingLastSuperAdmin($targetUser, $roles->pluck('id')->all(), null);
        }

        DB::transaction(function () use ($targetUser, $roles, $remark): void {
            $oldRoleNames = $targetUser->adminRoles()->orderBy('sort_order')->pluck('display_name')->all();

            UserRole::query()
                ->where('user_id', $targetUser->getKey())
                ->where('user_type', User::class)
                ->where('guard_name', (string) config('dberp.admin.guard', 'web'))
                ->delete();

            foreach ($roles as $role) {
                UserRole::query()->create([
                    'user_id' => $targetUser->getKey(),
                    'user_type' => User::class,
                    'role_id' => $role->getKey(),
                    'guard_name' => (string) config('dberp.admin.guard', 'web'),
                ]);
            }

            $this->auditService->log('dberp.admin.user_roles_synced', $targetUser, [
                'roles' => $oldRoleNames,
            ], [
                'roles' => $roles->sortBy('sort_order')->pluck('display_name')->all(),
                'remark' => $remark,
            ]);
        });
    }

    /**
     * @param  list<string>  $permissionCodes
     */
    public function syncRolePermissions(DberpAdminRole $role, array $permissionCodes, ?User $actingUser = null, ?string $remark = null, bool $skipAuthorization = false): void
    {
        if (! $skipAuthorization) {
            $this->assertCanManageRole($actingUser, $role);
        }

        // 角色表单展示来自 Registry，但 pivot 写入依赖 core_permissions；升级后若未同步目录会导致勾选无法落库。
        $this->syncPermissionCatalog();

        $validCodes = $role->is_super_admin
            ? $this->permissionRegistry->codes()
            : array_values(array_intersect($permissionCodes, $this->permissionRegistry->codes()));

        /** @var Collection<int, DberpAdminPermission> $permissions */
        $permissions = DberpAdminPermission::query()
            ->whereIn('name', $validCodes)
            ->orderBy('sort_order')
            ->get();

        DB::transaction(function () use ($role, $permissions, $remark): void {
            $oldPermissionNames = $role->permissions()->orderBy('sort_order')->pluck('display_name')->all();

            RolePermission::query()->where('role_id', $role->getKey())->delete();

            foreach ($permissions as $permission) {
                RolePermission::query()->create([
                    'role_id' => $role->getKey(),
                    'permission_id' => $permission->getKey(),
                    'effect' => PermissionEffect::Allow->value,
                ]);
            }

            $this->auditService->log('dberp.admin.role_permissions_synced', $role, [
                'permissions' => $oldPermissionNames,
            ], [
                'permissions' => $permissions->pluck('display_name')->all(),
                'remark' => $remark,
            ]);
        });
    }

    public function updateUserStatus(User $targetUser, AdminUserStatus $status, ?User $actingUser = null, ?string $remark = null): void
    {
        $this->assertCanManageUserStatus($actingUser, $targetUser, $status);
        $this->guardAgainstRemovingLastSuperAdmin($targetUser, null, $status);

        $oldStatus = $targetUser->status;

        $targetUser->forceFill([
            'status' => $status->value,
        ])->save();

        $this->auditService->log('dberp.admin.user_status_updated', $targetUser, [
            'status' => $oldStatus instanceof AdminUserStatus ? $oldStatus->label() : (string) $oldStatus,
        ], [
            'status' => $status->label(),
            'remark' => $remark,
        ]);
    }

    public function updateRoleStatus(DberpAdminRole $role, AdminRoleStatus $status, ?User $actingUser = null, ?string $remark = null): void
    {
        $this->assertCanManageRoleStatus($actingUser, $role);

        $currentStatus = $role->status instanceof AdminRoleStatus
            ? $role->status
            : AdminRoleStatus::from((string) $role->status);

        if ($currentStatus === $status) {
            return;
        }

        if ($role->isSystemPreset() && $status === AdminRoleStatus::Disabled) {
            $this->throwProtectedOperation(
                'dberp.admin.role_status_blocked',
                (string) __('dberp.errors.admin_role_system_cannot_disable'),
                $role,
                ['remark' => $remark],
            );
        }

        $this->guardAgainstDisablingLastSuperAdminRole($role, $status);

        $role->forceFill([
            'status' => $status->value,
        ])->save();

        $this->auditService->log('dberp.admin.role_status_updated', $role, [
            'status' => $currentStatus->label(),
        ], [
            'status' => $status->label(),
            'remark' => $remark,
        ]);
    }

    public function deleteRole(DberpAdminRole $role, ?User $actingUser = null, ?string $remark = null): void
    {
        $this->assertCanDeleteRole($actingUser, $role);

        if ($role->isSystemPreset()) {
            $this->throwProtectedOperation(
                'dberp.admin.role_delete_blocked',
                (string) __('dberp.errors.admin_role_system_cannot_delete'),
                $role,
                ['remark' => $remark],
            );
        }

        if ($role->users()->exists()) {
            $this->throwProtectedOperation(
                'dberp.admin.role_delete_blocked',
                '角色下仍有绑定管理员，请先调整管理员角色后再删除。',
                $role,
                ['remark' => $remark],
            );
        }

        if ($role->is_super_admin && DberpAdminRole::query()->where('is_super_admin', true)->count() <= 1) {
            $this->throwProtectedOperation(
                'dberp.admin.role_delete_blocked',
                '不能删除最后一个超级管理员角色。',
                $role,
                ['remark' => $remark],
            );
        }

        $oldValues = [
            'display_name' => $role->display_name,
            'name' => $role->name,
            'remark' => $remark,
        ];

        DB::transaction(function () use ($role): void {
            RolePermission::query()->where('role_id', $role->getKey())->delete();
            UserRole::query()->where('role_id', $role->getKey())->delete();
            $role->delete();
        });

        $this->auditService->log('dberp.admin.role_deleted', null, $oldValues, null);
    }

    /**
     * @param  Collection<int, DberpAdminRole>  $roles
     */
    private function assertCanManageUserRoles(?User $actingUser, Collection $roles): void
    {
        if (! $actingUser instanceof User) {
            return;
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::EDIT_ADMIN_USER)) {
            $this->throwAuthorizationDenied(
                'dberp.admin.user_roles_blocked',
                '当前账号无权维护管理员角色。',
                $actingUser,
                ['roles' => $roles->pluck('display_name')->all()],
            );
        }

        if ($roles->contains(fn (DberpAdminRole $role): bool => $role->is_super_admin) && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied(
                'dberp.admin.user_roles_blocked',
                '只有超级管理员可以分配高权限角色。',
                $actingUser,
                ['roles' => $roles->pluck('display_name')->all()],
            );
        }
    }

    private function assertCanManageUserStatus(?User $actingUser, User $targetUser, AdminUserStatus $status): void
    {
        if (! $actingUser instanceof User) {
            return;
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::DISABLE_ADMIN_USER)) {
            $this->throwAuthorizationDenied('dberp.admin.user_status_blocked', '当前账号无权变更管理员状态。', $targetUser);
        }

        if ($targetUser->isSuperAdmin() && $status === AdminUserStatus::Disabled && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied('dberp.admin.user_status_blocked', '只有超级管理员可以停用高权限管理员。', $targetUser);
        }

        if ($actingUser->is($targetUser) && $status === AdminUserStatus::Disabled) {
            $this->throwProtectedOperation('dberp.admin.user_status_blocked', '不能停用当前登录账号。', $targetUser);
        }
    }

    private function assertCanManageRole(?User $actingUser, DberpAdminRole $role): void
    {
        if (! $actingUser instanceof User) {
            return;
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::EDIT_ADMIN_ROLE)) {
            $this->throwAuthorizationDenied('dberp.admin.role_update_blocked', '当前账号无权维护角色。', $role);
        }

        if ($role->is_super_admin && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied('dberp.admin.role_update_blocked', '只有超级管理员可以修改高权限角色。', $role);
        }
    }

    private function assertCanManageRoleStatus(?User $actingUser, DberpAdminRole $role): void
    {
        if (! $actingUser instanceof User) {
            return;
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::DISABLE_ADMIN_ROLE)) {
            $this->throwAuthorizationDenied('dberp.admin.role_status_blocked', '当前账号无权调整角色状态。', $role);
        }

        if ($role->is_super_admin && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied('dberp.admin.role_status_blocked', '只有超级管理员可以调整高权限角色状态。', $role);
        }
    }

    private function assertCanDeleteRole(?User $actingUser, DberpAdminRole $role): void
    {
        if (! $actingUser instanceof User) {
            return;
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::DELETE_ADMIN_ROLE)) {
            $this->throwAuthorizationDenied('dberp.admin.role_delete_blocked', '当前账号无权删除角色。', $role);
        }

        if ($role->is_super_admin && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied('dberp.admin.role_delete_blocked', '只有超级管理员可以删除高权限角色。', $role);
        }
    }

    private function assertCanCreateRole(?User $actingUser, bool $hasPermissions): void
    {
        if (! $actingUser instanceof User) {
            return;
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::CREATE_ADMIN_ROLE)) {
            $this->throwAuthorizationDenied('dberp.admin.role_create_blocked', '当前账号无权新增角色。', $actingUser);
        }

        if ($hasPermissions && ! $this->permissionService->allows($actingUser, DberpPermissionService::MANAGE_ADMIN_ROLE_PERMISSIONS)) {
            $this->throwAuthorizationDenied('dberp.admin.role_create_blocked', '当前账号无权配置角色权限。', $actingUser);
        }
    }

    private function assertCanCopyRole(?User $actingUser, DberpAdminRole $role): void
    {
        if (! $actingUser instanceof User) {
            return;
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::CREATE_ADMIN_ROLE)
            || ! $this->permissionService->allows($actingUser, DberpPermissionService::COPY_ADMIN_ROLE)) {
            $this->throwAuthorizationDenied('dberp.admin.role_copy_blocked', '当前账号无权复制角色。', $role);
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::MANAGE_ADMIN_ROLE_PERMISSIONS)) {
            $this->throwAuthorizationDenied('dberp.admin.role_copy_blocked', '当前账号无权复制角色权限。', $role);
        }

        if ($role->is_super_admin && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied('dberp.admin.role_copy_blocked', '只有超级管理员可以复制高权限角色。', $role);
        }
    }

    private function assertCanUpdateRole(?User $actingUser, DberpAdminRole $role, bool $isBasicChanged, bool $isPermissionsChanged, bool $isStatusChanged): void
    {
        if (! $actingUser instanceof User) {
            return;
        }

        if ($role->is_super_admin && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied('dberp.admin.role_update_blocked', '只有超级管理员可以修改高权限角色。', $role);
        }

        if ($isBasicChanged && ! $this->permissionService->allows($actingUser, DberpPermissionService::EDIT_ADMIN_ROLE)) {
            $this->throwAuthorizationDenied('dberp.admin.role_update_blocked', '当前账号无权编辑角色资料。', $role);
        }

        if ($isPermissionsChanged && ! $this->permissionService->allows($actingUser, DberpPermissionService::MANAGE_ADMIN_ROLE_PERMISSIONS)) {
            $this->throwAuthorizationDenied('dberp.admin.role_permissions_blocked', '当前账号无权配置角色权限。', $role);
        }

        if ($isStatusChanged && ! $this->permissionService->allows($actingUser, DberpPermissionService::DISABLE_ADMIN_ROLE)) {
            $this->throwAuthorizationDenied('dberp.admin.role_status_blocked', '当前账号无权调整角色状态。', $role);
        }
    }

    /**
     * @param  Collection<int, DberpAdminRole>  $roles
     */
    private function assertCanCreateUser(?User $actingUser, Collection $roles): void
    {
        if (! $actingUser instanceof User) {
            return;
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::CREATE_ADMIN_USER)) {
            $this->throwAuthorizationDenied(
                'dberp.admin.user_create_blocked',
                '当前账号无权新增管理员。',
                $actingUser,
                ['roles' => $roles->pluck('display_name')->all()],
            );
        }

        if ($roles->contains(fn (DberpAdminRole $role): bool => $role->is_super_admin) && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied(
                'dberp.admin.user_create_blocked',
                '只有超级管理员可以分配高权限角色。',
                $actingUser,
                ['roles' => $roles->pluck('display_name')->all()],
            );
        }
    }

    /**
     * @param  Collection<int, DberpAdminRole>  $roles
     */
    private function assertCanUpdateUser(?User $actingUser, User $targetUser, Collection $roles, AdminUserStatus $status): void
    {
        if (! $actingUser instanceof User) {
            return;
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::EDIT_ADMIN_USER)) {
            $this->throwAuthorizationDenied(
                'dberp.admin.user_update_blocked',
                '当前账号无权编辑管理员。',
                $targetUser,
                ['roles' => $roles->pluck('display_name')->all()],
            );
        }

        if ($targetUser->isSuperAdmin() && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied('dberp.admin.user_update_blocked', '只有超级管理员可以编辑高权限管理员。', $targetUser);
        }

        if ($roles->contains(fn (DberpAdminRole $role): bool => $role->is_super_admin) && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied(
                'dberp.admin.user_update_blocked',
                '只有超级管理员可以分配高权限角色。',
                $targetUser,
                ['roles' => $roles->pluck('display_name')->all()],
            );
        }

        if ($targetUser->isSuperAdmin() && $status === AdminUserStatus::Disabled && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied('dberp.admin.user_status_blocked', '只有超级管理员可以停用高权限管理员。', $targetUser);
        }
    }

    private function assertCanResetUserPassword(?User $actingUser, User $targetUser): void
    {
        if (! $actingUser instanceof User) {
            return;
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::RESET_ADMIN_USER_PASSWORD)) {
            $this->throwAuthorizationDenied('dberp.admin.user_password_reset_blocked', '当前账号无权重置管理员密码。', $targetUser);
        }

        if ($targetUser->isSuperAdmin() && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied('dberp.admin.user_password_reset_blocked', '只有超级管理员可以重置高权限管理员密码。', $targetUser);
        }
    }

    private function assertCanDeleteUser(?User $actingUser, User $targetUser): void
    {
        if (! $actingUser instanceof User) {
            $this->throwAuthorizationDenied('dberp.admin.user_delete_blocked', '当前操作需要登录管理员身份。', $targetUser);
        }

        if (! $this->permissionService->allows($actingUser, DberpPermissionService::DELETE_ADMIN_USER)) {
            $this->throwAuthorizationDenied('dberp.admin.user_delete_blocked', '当前账号无权删除管理员。', $targetUser);
        }

        if ($targetUser->isSuperAdmin() && ! $actingUser->isSuperAdmin()) {
            $this->throwAuthorizationDenied('dberp.admin.user_delete_blocked', '只有超级管理员可以删除高权限管理员。', $targetUser);
        }
    }

    /**
     * @param  list<int>  $roleIdsAfter
     */
    private function guardAgainstSelfLockout(?User $actingUser, User $targetUser, array $roleIdsAfter, AdminUserStatus $statusAfter): void
    {
        if (! $actingUser instanceof User || ! $actingUser->is($targetUser)) {
            return;
        }

        if ($statusAfter === AdminUserStatus::Disabled) {
            $this->throwProtectedOperation('dberp.admin.user_status_blocked', '不能停用当前登录账号。', $targetUser);
        }

        $hasRoleAfter = $roleIdsAfter !== [];
        $hasDirectPermissions = $targetUser->dberpPermissionGrants()->exists();

        if (! $hasRoleAfter && ! $hasDirectPermissions) {
            $this->throwProtectedOperation('dberp.admin.user_roles_blocked', '不能移除当前登录账号的后台访问能力。', $targetUser);
        }
    }

    /**
     * @param  list<int>  $roleIds
     * @return Collection<int, DberpAdminRole>
     */
    private function resolveRoles(array $roleIds): Collection
    {
        $normalizedRoleIds = collect($roleIds)
            ->map(fn (mixed $roleId): int => (int) $roleId)
            ->filter(fn (int $roleId): bool => $roleId > 0)
            ->unique()
            ->values()
            ->all();

        if ($normalizedRoleIds === []) {
            $this->throwProtectedOperation('dberp.admin.user_roles_blocked', '至少需要绑定一个角色。');
        }

        $roles = DberpAdminRole::query()
            ->whereIn('id', $normalizedRoleIds)
            ->get();

        if ($roles->count() !== count($normalizedRoleIds)) {
            $this->throwProtectedOperation('dberp.admin.user_roles_blocked', '所选角色不存在或已失效。');
        }

        if ($roles->contains(fn (DberpAdminRole $role): bool => ! $role->isEnabled())) {
            $this->throwProtectedOperation(
                'dberp.admin.user_roles_blocked',
                '只能绑定启用中的角色。',
                null,
                ['roles' => $roles->pluck('display_name')->all()],
            );
        }

        return $roles;
    }

    private function normalizeUserStatus(AdminUserStatus|string|null $status): AdminUserStatus
    {
        if ($status instanceof AdminUserStatus) {
            return $status;
        }

        if (! is_string($status) || $status === '') {
            return AdminUserStatus::Enabled;
        }

        return AdminUserStatus::from($status);
    }

    private function normalizeRoleStatus(AdminRoleStatus|string|null $status): AdminRoleStatus
    {
        if ($status instanceof AdminRoleStatus) {
            return $status;
        }

        if (! is_string($status) || $status === '') {
            return AdminRoleStatus::Enabled;
        }

        return AdminRoleStatus::from($status);
    }

    private function normalizeNullableString(mixed $value): ?string
    {
        $string = trim((string) ($value ?? ''));

        return $string === '' ? null : $string;
    }

    private function normalizeSortOrder(mixed $value): int
    {
        $sortOrder = (int) ($value ?? 100);

        return $sortOrder > 0 ? $sortOrder : 100;
    }

    /**
     * @param  list<string>  $permissionCodes
     * @return list<string>
     */
    private function resolvePermissionCodes(array $permissionCodes): array
    {
        return collect($permissionCodes)
            ->map(fn (mixed $permissionCode): string => (string) $permissionCode)
            ->filter(fn (string $permissionCode): bool => $permissionCode !== '')
            ->unique()
            ->values()
            ->intersect($this->permissionRegistry->codes())
            ->values()
            ->all();
    }

    private function generateRoleName(): string
    {
        do {
            $name = 'role_'.Str::lower(Str::random(10));
        } while (DberpAdminRole::query()->where('name', $name)->exists());

        return $name;
    }

    /**
     * @param  list<int>|null  $roleIdsAfter
     */
    private function guardAgainstRemovingLastSuperAdmin(User $targetUser, ?array $roleIdsAfter, ?AdminUserStatus $statusAfter): void
    {
        $currentStatus = $targetUser->status instanceof AdminUserStatus
            ? $targetUser->status
            : AdminUserStatus::from((string) ($targetUser->status ?? AdminUserStatus::Enabled->value));

        $willRemainEnabled = ($statusAfter ?? $currentStatus) !== AdminUserStatus::Disabled;
        $willRemainSuperAdmin = $roleIdsAfter === null
            ? $targetUser->isSuperAdmin()
            : DberpAdminRole::query()->whereIn('id', $roleIdsAfter)->where('is_super_admin', true)->exists();

        if ($willRemainEnabled && $willRemainSuperAdmin) {
            return;
        }

        if (! $targetUser->isSuperAdmin()) {
            return;
        }

        $activeSuperAdminCount = User::query()
            ->where('status', AdminUserStatus::Enabled->value)
            ->whereExists(function ($query): void {
                $query->selectRaw('1')
                    ->from('core_user_roles')
                    ->join('core_roles', 'core_roles.id', '=', 'core_user_roles.role_id')
                    ->whereColumn('core_user_roles.user_id', 'users.id')
                    ->where('core_user_roles.user_type', User::class)
                    ->where('core_roles.is_super_admin', true)
                    ->where('core_roles.status', AdminRoleStatus::Enabled->value);
            })
            ->count();

        if ($activeSuperAdminCount <= 1) {
            $this->throwProtectedOperation('dberp.admin.user_status_blocked', '不能停用或移除最后一个超级管理员。', $targetUser);
        }
    }

    private function guardAgainstDisablingLastSuperAdminRole(DberpAdminRole $role, AdminRoleStatus $statusAfter): void
    {
        if (! $role->is_super_admin || $statusAfter !== AdminRoleStatus::Disabled) {
            return;
        }

        $enabledSuperAdminRoleCount = DberpAdminRole::query()
            ->where('is_super_admin', true)
            ->where('status', AdminRoleStatus::Enabled->value)
            ->count();

        if ($enabledSuperAdminRoleCount <= 1) {
            $this->throwProtectedOperation('dberp.admin.role_status_blocked', '不能停用最后一个系统管理员角色。', $role);
        }
    }

    /**
     * @param  array<string, mixed>  $context
     */
    private function throwAuthorizationDenied(string $action, string $message, ?Model $auditable = null, array $context = []): never
    {
        $this->logBlockedAction($action, $message, $auditable, $context);

        throw new AuthorizationException($message);
    }

    /**
     * @param  array<string, mixed>  $context
     */
    private function throwProtectedOperation(string $action, string $message, ?Model $auditable = null, array $context = []): never
    {
        $this->logBlockedAction($action, $message, $auditable, $context);

        throw new DomainException($message);
    }

    /**
     * @param  array<string, mixed>  $context
     */
    private function logBlockedAction(string $action, string $message, ?Model $auditable = null, array $context = []): void
    {
        $this->auditService->log($action, $auditable, null, array_filter([
            'result' => 'blocked',
            'reason' => $message,
            ...$context,
        ], static fn (mixed $value): bool => $value !== null && $value !== [] && $value !== ''));
    }

    private function normalizeLoginAccount(string $value): string
    {
        $trimmed = trim($value);
        if ($trimmed === '') {
            throw new DomainException('登录账号不能为空。');
        }
        if (strlen($trimmed) > 64) {
            throw new DomainException('登录账号长度不能超过 64 个字符。');
        }
        if (! preg_match('/^[a-zA-Z0-9_.-]+$/', $trimmed)) {
            throw new DomainException('登录账号只能包含字母、数字、下划线、连字符与英文句点。');
        }

        return $trimmed;
    }

    private function assertLoginAccountUnique(string $loginAccount, mixed $ignoreUserId): void
    {
        $query = User::query()->where('login_account', $loginAccount);
        if ($ignoreUserId !== null) {
            $query->whereKeyNot($ignoreUserId);
        }
        if ($query->exists()) {
            throw new DomainException('登录账号已被使用，请更换后再试。');
        }
    }

    private function normalizeOptionalEmail(?string $value): ?string
    {
        if (! filled($value)) {
            return null;
        }
        $trimmed = trim((string) $value);
        if ($trimmed === '') {
            return null;
        }
        if (strlen($trimmed) > 255 || ! filter_var($trimmed, FILTER_VALIDATE_EMAIL)) {
            throw new DomainException('邮箱格式不正确。');
        }

        return $trimmed;
    }
}
