<?php

namespace Tests\Helpers;

use BookStack\Entities\Models\Entity;
use BookStack\Permissions\Models\EntityPermission;
use BookStack\Permissions\Models\RolePermission;
use BookStack\Settings\SettingService;
use BookStack\Users\Models\Role;
use BookStack\Users\Models\User;

class PermissionsProvider
{
    public function __construct(
        protected UserRoleProvider $userRoleProvider
    ) {
    }

    public function makeAppPublic(): void
    {
        $settings = app(SettingService::class);
        $settings->put('app-public', 'true');
    }

    /**
     * Grant role permissions to the provided user.
     */
    public function grantUserRolePermissions(User $user, array $permissions): void
    {
        $newRole = $this->userRoleProvider->createRole($permissions);
        $user->attachRole($newRole);
        $user->load('roles');
        $user->clearPermissionCache();
    }

    /**
     * Completely remove specific role permissions from the provided user.
     */
    public function removeUserRolePermissions(User $user, array $permissions): void
    {
        foreach ($permissions as $permissionName) {
            /** @var RolePermission $permission */
            $permission = RolePermission::query()
                ->where('name', '=', $permissionName)
                ->firstOrFail();

            $roles = $user->roles()->whereHas('permissions', function ($query) use ($permission) {
                $query->where('id', '=', $permission->id);
            })->get();

            /** @var Role $role */
            foreach ($roles as $role) {
                $role->detachPermission($permission);
            }

            $user->clearPermissionCache();
        }
    }

    /**
     * Change the owner of the given entity to the given user.
     */
    public function changeEntityOwner(Entity $entity, User $newOwner): void
    {
        $entity->owned_by = $newOwner->id;
        $entity->save();
        $entity->rebuildPermissions();
    }

    /**
     * Regenerate the permission for an entity.
     * Centralised to manage clearing of cached elements between requests.
     */
    public function regenerateForEntity(Entity $entity): void
    {
        $entity->rebuildPermissions();
    }

    /**
     * Set the given entity as having restricted permissions, and apply the given
     * permissions for the given roles.
     * @param string[] $actions
     * @param Role[] $roles
     */
    public function setEntityPermissions(Entity $entity, array $actions = [], array $roles = [], $inherit = false): void
    {
        $entity->permissions()->delete();

        $permissions = [];

        if (!$inherit) {
            // Set default permissions to not allow actions so that only the provided role permissions are at play.
            $permissions[] = ['role_id' => 0, 'view' => false, 'create' => false, 'update' => false, 'delete' => false];
        }

        foreach ($roles as $role) {
            $permissions[] = $this->actionListToEntityPermissionData($actions, $role->id);
        }

        $this->addEntityPermissionEntries($entity, $permissions);
    }

    public function addEntityPermission(Entity $entity, array $actionList, Role $role)
    {
        $permissionData = $this->actionListToEntityPermissionData($actionList, $role->id);
        $this->addEntityPermissionEntries($entity, [$permissionData]);
    }

    public function setFallbackPermissions(Entity $entity, array $actionList)
    {
        $entity->permissions()->where('role_id', '=', 0)->delete();
        $permissionData = $this->actionListToEntityPermissionData($actionList, 0);
        $this->addEntityPermissionEntries($entity, [$permissionData]);
    }

    /**
     * Disable inherited permissions on the given entity.
     * Effectively sets the "Other Users" UI permission option to not inherit, with no permissions.
     */
    public function disableEntityInheritedPermissions(Entity $entity): void
    {
        $entity->permissions()->where('role_id', '=', 0)->delete();
        $fallback = $this->actionListToEntityPermissionData([]);
        $this->addEntityPermissionEntries($entity, [$fallback]);
    }

    protected function addEntityPermissionEntries(Entity $entity, array $entityPermissionData): void
    {
        $entity->permissions()->createMany($entityPermissionData);
        $entity->load('permissions');
        $this->regenerateForEntity($entity);
    }

    /**
     * For the given simple array of string actions (view, create, update, delete), convert
     * the format to entity permission data, where permission is granted if the action is in the
     * given actionList array.
     */
    protected function actionListToEntityPermissionData(array $actionList, int $roleId = 0): array
    {
        $permissionData = ['role_id' => $roleId];
        foreach (EntityPermission::PERMISSIONS as $possibleAction) {
            $permissionData[$possibleAction] = in_array($possibleAction, $actionList);
        }

        return $permissionData;
    }
}