<?php

/**
 * @uri /role/:rolename 0
 */
class Role extends CfProtectedResource
{
    /**
     * @param $request
     * @param $rolename
     *
     * @rbacName View role
     * @rbacGroup Roles
     * @rbacAlias role.get
     * @rbacAllowedByDefault
     * @return Response
     */
    public function get($request, string $rolename)
    {
        $user = $this->username;
        $rolename = rawurldecode($rolename);

        $response = new Response($request);
        $payload = cfapi_role_get($user, $rolename);
        if ($payload) {
            $response->code = Response::OK;
            $response->body = $payload;
        } else {
            $response->code = Response::NOTFOUND;
        }


        return $response;
    }

    /**
     * @param $request
     * @param $rolename
     *
     * @rbacName Create role
     * @rbacGroup Roles
     * @rbacAlias role.create
     *
     * @return Response
     * @throws ResponseException
     */
    public function put($request, string $rolename)
    {
        $user = $this->username;
        $rolename = rawurldecode($rolename);

        $data = Utils::getValidJsonData($request->data);

        if (isset($data->description) && !is_string($data->description)) {
            throw new InvalidArgumentException('Description must be a string.');
        }

        $response = new Response($request);
        if (cfapi_role_put(
            $user,
            $rolename,
            $data->description ?? '',
            $data->includeContext ?? '',
            $data->excludeContext ?? ''
        )) {
            $response->code = Response::CREATED;
            AuditLogService::register([
                AuditLogFields::ACTOR => $this->username,
                AuditLogFields::OBJECT_TYPE => AuditLogObjectTypes::ROLE,
                AuditLogFields::OBJECT_ID => $rolename,
                AuditLogFields::OBJECT_NAME => $rolename,
                AuditLogFields::ACTION => AuditLogActions::CREATE,
                AuditLogFields::DETAILS => [
                    "Created role `$rolename`.",
                    [
                        'description' => $data->description ?? '',
                        'includedContext' => $data->includeContext ?? '',
                        'excludeContext' => $data->excludeContext ?? ''
                    ]
                ]
            ]);
        } else {
            $response->code = Response::INTERNALSERVERERROR;
        }

        return $response;
    }

    /**
     * @param $request
     * @param $rolename
     *
     * @rbacName Update role
     * @rbacGroup Roles
     * @rbacAlias role.update
     *
     * @return Response
     * @throws ResponseException
     */
    public function post($request, string $rolename)
    {
        $user = $this->username;
        $rolename = rawurldecode($rolename);
        $response = new Response($request);

        $data = Utils::getValidJsonData($request->data);

        if (isset($data->description) && !is_string($data->description)) {
            throw new InvalidArgumentException('Description must be a string.');
        }

        if (cfapi_role_post(
            $user,
            $rolename,
            $data->description ?? '',
            $data->includeContext ?? '',
            $data->excludeContext ?? ''
        )) {
            $response->code = Response::NOCONTENT;
            AuditLogService::register([
                AuditLogFields::ACTOR => $this->username,
                AuditLogFields::OBJECT_TYPE => AuditLogObjectTypes::ROLE,
                AuditLogFields::OBJECT_ID => $rolename,
                AuditLogFields::OBJECT_NAME => $rolename,
                AuditLogFields::ACTION => AuditLogActions::UPDATE,
                AuditLogFields::DETAILS => [
                    "Updated role `$rolename`.",
                    [
                        'description' => $data->description ?? '',
                        'includedContext' => $data->includeContext ?? '',
                        'excludeContext' => $data->excludeContext ?? ''
                    ]
                ]
            ]);
        } else {
            $response->code = Response::INTERNALSERVERERROR;
        }

        return $response;
    }

    /**
     * @param $request
     * @param $rolename
     *
     * @rbacName Delete role
     * @rbacGroup Roles
     * @rbacAlias role.delete
     *
     * @return Response
     */
    public function delete($request, string $rolename)
    {
        $user = $this->username;
        $rolename = rawurldecode($rolename);

        $response = new Response($request);

        if (cfapi_role_delete($user, $rolename)) {
            $response->code = Response::NOCONTENT;
            AuditLogService::register([
                AuditLogFields::ACTOR => $this->username,
                AuditLogFields::OBJECT_TYPE => AuditLogObjectTypes::ROLE,
                AuditLogFields::OBJECT_ID => $rolename,
                AuditLogFields::OBJECT_NAME => $rolename,
                AuditLogFields::ACTION => AuditLogActions::DELETE,
                AuditLogFields::DETAILS => ["Deleted role `$rolename`."]
            ]);
        } else {
            $response->code = Response::INTERNALSERVERERROR;
        }

        return $response;
    }
}

/**
 * @uri /role 1
 */
class RoleList extends CfProtectedResource
{
    /**
     * @param $request
     *
     * @rbacName Role list
     * @rbacGroup Roles
     * @rbacAlias role.list
     * @rbacAllowedByDefault
     * @return Response
     */
    public function get($request)
    {
        $user = $this->username;

        $response = new Response($request);
        $response->body = cfapi_role_list($user);
        $response->code = Response::OK;

        return $response;
    }
}

/**
 * @uri /external_role 0
 */
include_once __DIR__ . '/../lib/CfExternalRoles.php';

class ExternalRoles extends CfProtectedResource
{
    private $cfExternalRoles;

    public function __construct(array $parameters)
    {
        $this->cfExternalRoles = new CfExternalRoles(CfSettings::getInstance()->getConnection());
        parent::__construct($parameters);
    }

    /**
     * @param $request
     *
     * @return Response
     */
    public function get($request)
    {
        $data = $_GET;
        $response = new Response($request);
        $response->body = json_encode($this->cfExternalRoles->getExternalRoleByInternal($data['internal_role']));
        $response->code = Response::OK;

        return $response;
    }

    /**
     * @param $request
     *
     * @return Response
     * @throws Exception
     * @throws ResponseException
     */
    public function post($request)
    {

        $data = Utils::getValidJsonData($request->data);
        $this->validateData($data);

        $res = $this->cfExternalRoles->addRoleMap($data->external_role, $data->internal_role);
        $response = new Response($request);
        $response->body = json_encode($res);
        $response->code = Response::OK;

        return $response;
    }

    /**
     * @param $request
     *
     * @return Response
     * @throws Exception
     * @throws ResponseException
     */
    public function patch($request)
    {

        $data = Utils::getValidJsonData($request->data);
        $this->validateData($data);

        $res = $this->cfExternalRoles->editRoleMap($data->external_role, $data->internal_role);
        $response = new Response($request);
        $response->body = json_encode($res);
        $response->code = Response::OK;

        return $response;
    }

    /**
     * @param $request
     *
     * @return Response
     * @throws Exception
     * @throws ResponseException
     */
    public function delete($request)
    {

        $data = $_GET;
        $res = $this->cfExternalRoles->deleteRoleMap($data['internal_role']);
        $response = new Response($request);
        $response->body = json_encode($res);
        $response->code = Response::OK;

        return $response;
    }

    /**
     * @param $data
     *
     * @throws Exception
     */
    private function validateData($data)
    {

        if (!isset($data->external_role)) {
            throw new Exception('External_role field is required.');
        }

        if (!isset($data->internal_role)) {
            throw new Exception('Internal_role field is required.');
        }
    }

}

/**
 * @uri /default_role
 */
class DefaultRole extends CfProtectedResource
{
    /**
     * @var CfRole
     */
    private $roleModel;

    /**
     * DefaultRole constructor.
     *
     * @param array $parameters
     */
    public function __construct(array $parameters)
    {
        $this->roleModel = new CfRole();
        parent::__construct($parameters);
    }

    /**
     * @param $request
     *
     * @rbacName View default role
     * @rbacGroup Roles
     * @rbacAlias default-role.get
     *
     * @return Response
     * @throws Exception
     */
    public function get($request)
    {
        $response = new Response($request);
        $response->body = json_encode($this->roleModel->getDefaultRole());
        $response->code = Response::OK;

        return $response;
    }

    /**
     * @param $request
     *
     * @rbacName Change default role
     * @rbacGroup Roles
     * @rbacAlias default-role.post
     *
     * @return Response
     * @throws ResponseException
     */
    public function post($request)
    {
        $data = Utils::getValidJsonData($request->data);
        $response = new Response($request);
        $role = rawurldecode($data->name);
        $response->body = json_encode($this->roleModel->setDefaultRole($role));
        $response->code = Response::OK;

        AuditLogService::register([
            AuditLogFields::ACTOR => $this->username,
            AuditLogFields::OBJECT_TYPE => AuditLogObjectTypes::ROLE,
            AuditLogFields::OBJECT_ID => $role,
            AuditLogFields::OBJECT_NAME => $role,
            AuditLogFields::ACTION => AuditLogActions::UPDATE,
            AuditLogFields::DETAILS => ["Changed default role to `$role`."]
        ]);

        return $response;
    }
}

/**
 * @uri /role/:role/permissions
 */
class RolePermission extends CfProtectedResource
{
    private $roles;
    private $cfRbac;

    public function __construct($parameters = array())
    {
        parent::__construct($parameters);

        $cfUser = new CfUsers($this->username);
        $this->roles = $cfUser->getUserRoles();
        $this->cfRbac = new CfRBAC();
    }

    /**
     * @param $request
     * @param $role
     *
     * @rbacName Get role permissions
     * @rbacGroup RBAC
     * @rbacAlias rolePermission.get
     *
     * @return Response
     */
    public function get($request, $role)
    {
        $role = rawurldecode($role);
        $response = new Response($request);
        $permissions = $this->cfRbac->getPermissionsByRoles([$role]);
        $response->body = json_encode($permissions);
        $response->code = Response::OK;

        return $response;
    }

    /**
     * @param $request
     * @param $role
     *
     * @rbacName Assign permissions to role
     * @rbacGroup RBAC
     * @rbacAlias rolePermission.update
     *
     * @return Response
     * @throws ResponseException
     */
    public function put($request, $role)
    {
        $permissions = Utils::getValidJsonData($request->data);
        $response = new Response($request);
        $role = rawurldecode($role);
        $rolePermissions = array_map(function ($item) {
            return $item['alias'];
        }, $this->cfRbac->getPermissionsByRoles([$role]));

        $revokedPermissions = array_diff($rolePermissions, $permissions);
        $assignedPermissions = array_diff($permissions, $rolePermissions);

        $this->cfRbac->revokeAllPermissions($role);

        if (sizeof($permissions) > 0) {
            foreach ($permissions as $alias) {
                if ($this->cfRbac->isPermissionExist($alias)) {
                    $this->cfRbac->assignPermission($role, $alias);
                } else {
                    $response->code = Response::NOTFOUND;
                    $response->body = sprintf('Permission %s does not exist', $alias);
                    return $response;
                }
            }
            $details = [];
            if (sizeof($assignedPermissions) > 0) {
                $details['assigned aliases'] = array_values($assignedPermissions);
            }
            if (sizeof($revokedPermissions) > 0) {
                $details['revoked aliases'] = array_values($revokedPermissions);
            }
            AuditLogService::register([
                AuditLogFields::ACTOR => $this->username,
                AuditLogFields::OBJECT_TYPE => AuditLogObjectTypes::ROLE,
                AuditLogFields::OBJECT_NAME => $role,
                AuditLogFields::OBJECT_ID => $role,
                AuditLogFields::ACTION => AuditLogActions::RBAC_UPDATED,
                AuditLogFields::DETAILS => [
                    "Updated RBAC permissions for role `$role`.",
                    $details
                ]
            ]);
        }

        $response->code = Response::CREATED;
        return $response;
    }

    /**
     * @param $request
     * @param $role
     *
     * @rbacName Assign permissions to role
     * @rbacGroup RBAC
     * @rbacAlias rolePermission.update
     *
     * @return Response
     * @throws ResponseException
     */
    public function post($request, $role)
    {
        $permissions = Utils::getValidJsonData($request->data);
        $role = rawurldecode($role);
        $response = new Response($request);

        if (sizeof($permissions) > 0) {
            foreach ($permissions as $alias) {
                if ($this->cfRbac->isPermissionExist($alias)) {
                    $this->cfRbac->assignPermission($role, $alias);
                } else {
                    $response->code = Response::NOTFOUND;
                    $response->body = sprintf('Permission %s does not exist', $alias);
                    return $response;
                }
            }
            AuditLogService::register([
                AuditLogFields::ACTOR => $this->username,
                AuditLogFields::OBJECT_TYPE => AuditLogObjectTypes::ROLE,
                AuditLogFields::OBJECT_ID => $role,
                AuditLogFields::OBJECT_NAME => $role,
                AuditLogFields::ACTION => AuditLogActions::RBAC_UPDATED,
                AuditLogFields::DETAILS => ["Updated RBAC permissions for role `$role`."]
            ]);
        }

        $response->code = Response::CREATED;
        return $response;
    }

    /**
     * @param $request
     * @param $role
     *
     * @rbacName Revoke permissions from role
     * @rbacGroup RBAC
     * @rbacAlias rolePermission.delete
     *
     * @return Response
     * @throws ResponseException
     */
    public function delete($request, $role)
    {
        $response = new Response($request);
        $role = rawurldecode($role);
        $permissions = Utils::getValidJsonData($request->data);

        if (sizeof($permissions) > 0) {
            foreach ($permissions as $alias) {
                if ($this->cfRbac->isRolePermissionExist($role, $alias)) {
                    $this->cfRbac->revokePermission($role, $alias);
                } else {
                    $response->code = Response::NOTFOUND;
                    $response->body = sprintf('Role %s does not have permission %s', $role, $alias);
                    return $response;
                }
            }
            AuditLogService::register([
                AuditLogFields::ACTOR => $this->username,
                AuditLogFields::OBJECT_TYPE => AuditLogObjectTypes::ROLE,
                AuditLogFields::OBJECT_ID => $role,
                AuditLogFields::OBJECT_NAME => $role,
                AuditLogFields::ACTION => AuditLogActions::RBAC_UPDATED,
                AuditLogFields::DETAILS => ["Revoked RBAC permissions from role.", ['aliases' => $permissions]]
            ]);
        }

        $response->code = Response::NOCONTENT;
        return $response;
    }
}
