import datetime
import json
import logging
import itertools
import typing

from pillarsdk import Node
from pillarsdk import Project
from pillarsdk.exceptions import ResourceNotFound
from pillarsdk.exceptions import ForbiddenAccess

from flask import Blueprint
from flask import render_template
from flask import request
from flask import jsonify
from flask import session
from flask import abort
from flask import redirect
from flask import url_for
from flask_login import login_required, current_user
import werkzeug.exceptions as wz_exceptions

from pillar import current_app
from pillar.api.utils import utcnow
from pillar.web import system_util
from pillar.web import utils
from pillar.web.nodes import finders
from pillar.web.utils.jstree import jstree_get_children
import pillar.extension

from .forms import ProjectForm
from .forms import NodeTypeForm

blueprint = Blueprint('projects', __name__)
log = logging.getLogger(__name__)
SYNC_GROUP_NODE_NAME = 'Blender Sync'
IMAGE_SHARING_GROUP_NODE_NAME = 'Image sharing'


def project_view(projections: dict = None):
    """Endpoint decorator, translates project_url into an actual project."""

    import functools
    import pillarsdk

    if callable(projections):
        raise TypeError('Use with @project_view() <-- note the parentheses')

    def decorator(wrapped):
        @functools.wraps(wrapped)
        def wrapper(project_url, *args, **kwargs):
            if isinstance(project_url, pillarsdk.Resource):
                # This is already a resource, so this call probably is from one
                # view to another. Assume the caller knows what he's doing and
                # just pass everything along.
                return wrapped(project_url, *args, **kwargs)

            api = system_util.pillar_api()

            project = pillarsdk.Project.find_by_url(
                project_url,
                {'projection': projections} if projections else None,
                api=api)
            utils.attach_project_pictures(project, api)

            return wrapped(project, *args, **kwargs)

        return wrapper

    return decorator


@blueprint.route('/')
@login_required
def index():
    api = system_util.pillar_api()

    # Get all projects, except the home project.
    projects_user = Project.all({
        'where': {'user': current_user.objectid,
                  'category': {'$ne': 'home'}},
        'sort': '-_created'
    }, api=api)

    show_deleted_projects = request.args.get('deleted') is not None
    if show_deleted_projects:
        timeframe = utcnow() - datetime.timedelta(days=31)
        projects_deleted = Project.all({
            'where': {'user': current_user.objectid,
                      'category': {'$ne': 'home'},
                      '_deleted': True,
                      '_updated': {'$gt': timeframe}},
            'sort': '-_created'
        }, api=api)
    else:
        projects_deleted = {'_items': []}

    projects_shared = Project.all({
        'where': {'user': {'$ne': current_user.objectid},
                  'permissions.groups.group': {'$in': current_user.groups},
                  'is_private': True},
        'sort': '-_created',
        'embedded': {'user': 1},
    }, api=api)

    # Attach project images
    for project_list in (projects_user, projects_deleted, projects_shared):
        utils.mass_attach_project_pictures(project_list['_items'], api=api, header=False)

    return render_template(
        'projects/index_dashboard.html',
        gravatar=utils.gravatar(current_user.email, size=128),
        projects_user=projects_user['_items'],
        projects_deleted=projects_deleted['_items'],
        projects_shared=projects_shared['_items'],
        show_deleted_projects=show_deleted_projects,
        api=api)


@blueprint.route('/<project_url>/jstree')
def jstree(project_url):
    """Entry point to view a project as JSTree"""
    api = system_util.pillar_api()

    try:
        project = Project.find_one({
            'projection': {'_id': 1},
            'where': {'url': project_url}
        }, api=api)
    except ResourceNotFound:
        raise wz_exceptions.NotFound('No such project')

    return jsonify(items=jstree_get_children(None, project._id))


@blueprint.route('/home/')
@login_required
def home_project():
    api = system_util.pillar_api()
    project = _home_project(api)

    # Get the synchronised Blender versions
    project_id = project['_id']
    synced_versions = synced_blender_versions(project_id, api)

    extra_context = {
        'synced_versions': synced_versions,
        'show_addon_download_buttons': True,
    }

    return render_project(project, api, extra_context)


@blueprint.route('/home/images')
@login_required
def home_project_shared_images():
    api = system_util.pillar_api()
    project = _home_project(api)

    # Get the shared images
    project_id = project['_id']
    image_nodes = shared_image_nodes(project_id, api)

    extra_context = {
        'shared_images': image_nodes,
        'show_addon_download_buttons': current_user.has_role('subscriber', 'demo'),
    }

    return render_project(project, api, extra_context,
                          template_name='projects/home_images.html')


def _home_project(api):
    try:
        project = Project.find_from_endpoint('/bcloud/home-project', api=api)
    except ResourceNotFound:
        log.warning('Home project for user %s not found', current_user.objectid)
        raise wz_exceptions.NotFound('No such project')

    return project


def synced_blender_versions(home_project_id, api):
    """Returns a list of Blender versions with synced settings.

    Returns a list of {'version': '2.77', 'date': datetime.datetime()} dicts.
    Returns an empty list if no Blender versions were synced.
    """

    sync_group = Node.find_first({
        'where': {'project': home_project_id,
                  'node_type': 'group',
                  'parent': None,
                  'name': SYNC_GROUP_NODE_NAME},
        'projection': {'_id': 1}},
        api=api)

    if not sync_group:
        return []

    sync_nodes = Node.all(
        {
            'where': {'project': home_project_id,
                      'node_type': 'group',
                      'parent': sync_group['_id']},
            'projection': {
                'name': 1,
                '_updated': 1,
            },
            'sort': [('_updated', -1)],
        },
        api=api)

    sync_nodes = sync_nodes._items
    if not sync_nodes:
        return []

    return [{'version': node.name, 'date': node._updated}
            for node in sync_nodes]


def shared_image_nodes(home_project_id, api):
    """Returns a list of pillarsdk.Node objects."""

    parent_group = Node.find_first({
        'where': {'project': home_project_id,
                  'node_type': 'group',
                  'parent': None,
                  'name': IMAGE_SHARING_GROUP_NODE_NAME},
        'projection': {'_id': 1}},
        api=api)

    if not parent_group:
        log.debug('No image sharing parent node found.')
        return []

    nodes = Node.all({
        'where': {'project': home_project_id,
                  'node_type': 'asset',
                  'properties.content_type': 'image',
                  'parent': parent_group['_id']},
        'sort': '-_created',
        'projection': {
            '_created': 1,
            'name': 1,
            'picture': 1,
            'short_code': 1,
        }},
        api=api)

    nodes = nodes._items or []
    for node in nodes:
        node.picture = utils.get_file(node.picture)

    return nodes


@blueprint.route('/home/jstree')
def home_jstree():
    """Entry point to view the home project as JSTree"""
    api = system_util.pillar_api()

    try:
        project = Project.find_from_endpoint('/bcloud/home-project',
                                             params={'projection': {
                                                 '_id': 1,
                                                 'permissions': 1,
                                                 'category': 1,
                                                 'user': 1}},
                                             api=api)
    except ResourceNotFound:
        raise wz_exceptions.NotFound('No such project')

    return jsonify(items=jstree_get_children(None, project._id))


@blueprint.route('/<project_url>/')
def view(project_url):
    """Entry point to view a project"""

    if request.args.get('format') == 'jstree':
        log.warning('projects.view(%r) endpoint called with format=jstree, '
                    'redirecting to proper endpoint. URL is %s; referrer is %s',
                    project_url, request.url, request.referrer)
        return redirect(url_for('projects.jstree', project_url=project_url))

    api = system_util.pillar_api()
    project = find_project_or_404(project_url,
                                  embedded={'header_node': 1},
                                  api=api)

    # Load the header video file, if there is any.
    header_video_file = None
    header_video_node = None
    if project.header_node and project.header_node.node_type == 'asset' and \
            project.header_node.properties.content_type == 'video':
        header_video_node = project.header_node
        header_video_file = utils.get_file(project.header_node.properties.file)
        header_video_node.picture = utils.get_file(header_video_node.picture)

    return render_project(project, api,
                          extra_context={'header_video_file': header_video_file,
                                         'header_video_node': header_video_node})


def project_navigation_links(project: typing.Type[Project], api) -> list:
    """Returns a list of nodes for the project, for top navigation display.

    Args:
        project: A Project object.
        api: the api client credential.

    Returns:
        A list of links for the Project.
        For example we display a link to the project blog if present, as well
        as pages. The list is structured as follows:

        [{'url': '/p/spring/about', 'label': 'About'},
        {'url': '/p/spring/blog', 'label': 'Blog'}]
    """

    links = []

    # Fetch the blog
    blog = Node.find_first({
        'where': {'project': project._id, 'node_type': 'blog', '_deleted': {'$ne': True}},
        'projection': {
            'name': 1,
        }
    }, api=api)

    if blog:
        links.append({'url': finders.find_url_for_node(blog), 'label': blog.name, 'slug': 'blog'})

    # Fetch pages
    pages = Node.all({
        'where': {'project': project._id, 'node_type': 'page', '_deleted': {'$ne': True}},
        'projection': {
            'name': 1,
            'properties.url': 1
        }
    }, api=api)

    # Process the results and append the links to the list
    for p in pages._items:
        links.append({'url': finders.find_url_for_node(p), 'label': p.name, 'slug': p.properties.url})

    return links


def render_project(project, api, extra_context=None, template_name=None):
    utils.attach_project_pictures(project, api)

    def load_latest(list_of_ids, node_type=None):
        """Loads a list of IDs in reversed order."""

        if not list_of_ids:
            return []

        # Construct query parameters outside the loop.
        projection = {'name': 1, 'user': 1, 'node_type': 1, 'project': 1,
                      'properties.url': 1, 'properties.content_type': 1,
                      'properties.duration_seconds': 1,
                      'picture': 1}
        params = {'projection': projection, 'embedded': {'user': 1}}

        if node_type == 'post':
            projection['properties.content'] = 1
        elif node_type == 'asset':
            projection['description'] = 1

        list_latest = []
        for node_id in reversed(list_of_ids or ()):
            try:
                node_item = Node.find(node_id, params, api=api)

                node_item.picture = utils.get_file(node_item.picture, api=api)
                list_latest.append(node_item)
            except ForbiddenAccess:
                pass
            except ResourceNotFound:
                log.warning('Project %s refers to removed node %s!',
                            project._id, node_id)

        return list_latest

    project.nodes_featured = load_latest(project.nodes_featured, node_type='asset')
    project.nodes_blog = load_latest(project.nodes_blog, node_type='post')

    # Merge featured assets and blog posts into one activity stream
    def sort_key(item):
        return item._created

    activities = itertools.chain(project.nodes_featured,
                                 project.nodes_blog)
    activity_stream = sorted(activities, key=sort_key, reverse=True)

    if extra_context is None:
        extra_context = {}

    if project.category == 'home' and not current_app.config['RENDER_HOME_AS_REGULAR_PROJECT']:
        template_name = template_name or 'projects/home_index.html'
        return render_template(
            template_name,
            gravatar=utils.gravatar(current_user.email, size=128),
            project=project,
            api=system_util.pillar_api(),
            **extra_context)

    if template_name is None:
        if request.args.get('embed'):
            embed_string = '_embed'
        else:
            embed_string = ''
        template_name = "projects/view{0}.html".format(embed_string)

    navigation_links = project_navigation_links(project, api)
    extension_sidebar_links = current_app.extension_sidebar_links(project)

    return render_template(template_name,
                           api=api,
                           project=project,
                           node=None,
                           show_node=False,
                           show_project=True,
                           og_picture=project.picture_16_9,
                           activity_stream=activity_stream,
                           navigation_links=navigation_links,
                           extension_sidebar_links=extension_sidebar_links,
                           **extra_context)


def render_node_page(project_url, page_url, api):
    """Custom behaviour for pages, which are nodes, but accessible on a custom
    route base.
    """

    # TODO: ensure this is not called for the home project, as it would
    # generate conflicting websites
    project = find_project_or_404(project_url, api=api)
    try:
        page = Node.find_one({
            'where': {
                'project': project['_id'],
                'node_type': 'page',
                'properties.url': page_url}}, api=api)
    except ResourceNotFound:
        raise wz_exceptions.NotFound('No such node')

    return project, page


@blueprint.route('/<project_url>/<node_id>')
def view_node(project_url, node_id):
    """Entry point to view a node in the context of a project"""
    # Some browsers mangle URLs and URL-encode /p/{p-url}/#node-id
    if node_id.startswith('#'):
        return redirect(url_for('projects.view_node',
                                project_url=project_url,
                                node_id=node_id[1:]),
                        code=301)  # permanent redirect

    theatre_mode = 't' in request.args
    api = system_util.pillar_api()
    # First we check if it's a simple string, in which case we are looking for
    # a static page. Maybe we could use bson.objectid.ObjectId.is_valid(node_id)
    project: typing.Optional[Project] = None
    if not utils.is_valid_id(node_id):
        # raise wz_exceptions.NotFound('No such node')
        project, node = render_node_page(project_url, node_id, api)
    else:
        # Fetch the node before the project. If this user has access to the
        # node, we should be able to get the project URL too.
        try:
            node = Node.find(node_id, api=api)
        except ForbiddenAccess:
            return render_template('errors/403.html'), 403
        except ResourceNotFound:
            raise wz_exceptions.NotFound('No such node')

        try:
            project = Project.find_one({'where': {"url": project_url, '_id': node.project}},
                                       api=api)
        except ResourceNotFound:
            if theatre_mode:
                pass  # In theatre mode, we don't need access to the project at all.
            else:
                raise wz_exceptions.NotFound('No such project')

    navigation_links = []
    extension_sidebar_links = ''
    og_picture = node.picture = utils.get_file(node.picture, api=api)
    if project:
        utils.attach_project_pictures(project, api)
        if not node.picture:
            og_picture = project.picture_16_9
        navigation_links = project_navigation_links(project, api)
        extension_sidebar_links = current_app.extension_sidebar_links(project)

    # Append _theatre to load the proper template
    theatre = '_theatre' if theatre_mode else ''

    if node.node_type == 'page':
        return render_template('nodes/custom/page/view_embed.html',
                               api=api,
                               node=node,
                               project=project,
                               navigation_links=navigation_links,
                               extension_sidebar_links=extension_sidebar_links,
                               og_picture=og_picture,)

    return render_template('projects/view{}.html'.format(theatre),
                           api=api,
                           project=project,
                           node=node,
                           show_node=True,
                           show_project=False,
                           og_picture=og_picture,
                           navigation_links=navigation_links,
                           extension_sidebar_links=extension_sidebar_links,)


def find_project_or_404(project_url, embedded=None, api=None):
    """Aborts with a NotFound exception when the project cannot be found."""

    params = {'where': {"url": project_url}}
    if embedded:
        params['embedded'] = embedded

    try:
        project = Project.find_one(params, api=api)
    except ResourceNotFound:
        raise wz_exceptions.NotFound('No such project')

    return project


@blueprint.route('/<project_url>/search')
def search(project_url):
    """Search into a project"""
    api = system_util.pillar_api()
    project = find_project_or_404(project_url, api=api)
    utils.attach_project_pictures(project, api)

    return render_template('nodes/search.html',
                           project=project,
                           og_picture=project.picture_header)


@blueprint.route('/<project_url>/edit', methods=['GET', 'POST'])
@login_required
def edit(project_url):
    api = system_util.pillar_api()
    # Fetch the Node or 404
    try:
        project = Project.find_one({'where': {'url': project_url}}, api=api)
        # project = Project.find(project_url, api=api)
    except ResourceNotFound:
        abort(404)
    utils.attach_project_pictures(project, api)
    form = ProjectForm(
        project_id=project._id,
        name=project.name,
        url=project.url,
        summary=project.summary,
        description=project.description,
        is_private='GET' not in project.permissions.world,
        category=project.category,
        status=project.status,
    )

    if form.validate_on_submit():
        project = Project.find(project._id, api=api)
        project.name = form.name.data
        project.url = form.url.data
        project.summary = form.summary.data
        project.description = form.description.data
        project.category = form.category.data
        project.status = form.status.data
        if form.picture_square.data:
            project.picture_square = form.picture_square.data
        if form.picture_header.data:
            project.picture_header = form.picture_header.data
        if form.picture_16_9.data:
            project.picture_16_9 = form.picture_16_9.data

        # Update world permissions from is_private checkbox
        if form.is_private.data:
            project.permissions.world = []
        else:
            project.permissions.world = ['GET']

        project.update(api=api)
        # Reattach the pictures
        utils.attach_project_pictures(project, api)
    else:
        if project.picture_square:
            form.picture_square.data = project.picture_square._id
        if project.picture_header:
            form.picture_header.data = project.picture_header._id
        if project.picture_16_9:
            form.picture_16_9.data = project.picture_16_9._id

    # List of fields from the form that should be hidden to regular users
    if current_user.has_role('admin'):
        hidden_fields = []
    else:
        hidden_fields = ['url', 'status', 'is_private', 'category']

    return render_template('projects/edit.html',
                           form=form,
                           hidden_fields=hidden_fields,
                           project=project,
                           ext_pages=find_extension_pages(),
                           api=api)


def find_extension_pages() -> typing.List[pillar.extension.PillarExtension]:
    """Returns a list of Pillar extensions that have project settings pages."""

    return [ext for ext in current_app.pillar_extensions.values()
            if ext.has_project_settings]


@blueprint.route('/<project_url>/edit/node-type')
@login_required
def edit_node_types(project_url):
    api = system_util.pillar_api()
    # Fetch the project or 404
    try:
        project = Project.find_one({
            'where': '{"url" : "%s"}' % (project_url)}, api=api)
    except ResourceNotFound:
        return abort(404)

    utils.attach_project_pictures(project, api)

    return render_template('projects/edit_node_types.html',
                           api=api,
                           ext_pages=find_extension_pages(),
                           project=project)


@blueprint.route('/<project_url>/e/node-type/<node_type_name>', methods=['GET', 'POST'])
@login_required
def edit_node_type(project_url, node_type_name):
    api = system_util.pillar_api()
    # Fetch the Node or 404
    try:
        project = Project.find_one({'where': {'url': project_url}}, api=api)
    except ResourceNotFound:
        return abort(404)

    utils.attach_project_pictures(project, api)
    node_type = project.get_node_type(node_type_name)
    form = NodeTypeForm()

    if form.validate_on_submit():
        # Update dynamic & form schemas
        dyn_schema = json.loads(form.dyn_schema.data)
        node_type.dyn_schema = dyn_schema
        form_schema = json.loads(form.form_schema.data)
        node_type.form_schema = form_schema
        node_type.description = form.description.data

        # Update permissions
        permissions = json.loads(form.permissions.data)
        node_type.permissions = permissions

        project.update(api=api)
    elif request.method == 'GET':
        form.project_id.data = project._id
        if node_type:
            form.name.data = node_type.name
            form.description.data = node_type.description
            form.parent.data = node_type.parent

            dyn_schema = node_type.dyn_schema.to_dict()
            form_schema = node_type.form_schema.to_dict()
            if 'permissions' in node_type:
                permissions = node_type.permissions.to_dict()
            else:
                permissions = {}

            form.form_schema.data = json.dumps(form_schema, indent=4)
            form.dyn_schema.data = json.dumps(dyn_schema, indent=4)
            form.permissions.data = json.dumps(permissions, indent=4)

    if request.method == 'POST':
        # Send back a JSON response, as this is actually called
        # from JS instead of rendered as page.
        if form.errors:
            resp = jsonify({'_message': str(form.errors)})
            resp.status_code = 400
            return resp
        return jsonify({'_message': 'ok'})

    return render_template('projects/edit_node_type_embed.html',
                           form=form,
                           project=project,
                           api=api,
                           node_type=node_type)


@blueprint.route('/<project_url>/edit/sharing', methods=['GET', 'POST'])
@login_required
def sharing(project_url):
    api = system_util.pillar_api()
    # Fetch the project or 404
    try:
        project = Project.find_one({
            'where': '{"url" : "%s"}' % (project_url)}, api=api)
    except ResourceNotFound:
        return abort(404)

    # Fetch users that are part of the admin group
    users = project.get_users(api=api)
    for user in users['_items']:
        user['avatar'] = utils.gravatar(user['email'])

    if request.method == 'POST':
        user_id = request.form['user_id']
        action = request.form['action']
        try:
            if action == 'add':
                user = project.add_user(user_id, api=api)
            elif action == 'remove':
                user = project.remove_user(user_id, api=api)
        except ResourceNotFound:
            log.info('/p/%s/edit/sharing: User %s not found', project_url, user_id)
            return jsonify({'_status': 'ERROR',
                            'message': 'User %s not found' % user_id}), 404

        # Add gravatar to user
        user['avatar'] = utils.gravatar(user['email'])
        return jsonify(user)

    utils.attach_project_pictures(project, api)

    return render_template('projects/sharing.html',
                           api=api,
                           project=project,
                           ext_pages=find_extension_pages(),
                           users=users['_items'])


@blueprint.route('/e/add-featured-node', methods=['POST'])
@login_required
def add_featured_node():
    """Feature a node in a project. This method belongs here, because it affects
    the project node itself, not the asset.
    """
    api = system_util.pillar_api()
    node = Node.find(request.form['node_id'], api=api)
    action = project_update_nodes_list(node, project_id=node.project, list_name='featured')
    return jsonify(status='success', data=dict(action=action))


@blueprint.route('/e/move-node', methods=['POST'])
@login_required
def move_node():
    """Move a node within a project. While this affects the node.parent prop, we
    keep it in the scope of the project."""
    node_id = request.form['node_id']
    dest_parent_node_id = request.form.get('dest_parent_node_id')

    api = system_util.pillar_api()
    node = Node.find(node_id, api=api)
    # Get original parent id for clearing template fragment on success
    previous_parent_id = node.parent
    if dest_parent_node_id:
        node.parent = dest_parent_node_id
    elif node.parent:
        node.parent = None
    node.update(api=api)
    return jsonify(status='success', data=dict(message='node moved'))


@blueprint.route('/e/delete-node', methods=['POST'])
@login_required
def delete_node():
    """Delete a node"""
    api = system_util.pillar_api()
    node = Node.find(request.form['node_id'], api=api)
    if not node.has_method('DELETE'):
        return abort(403)

    node.delete(api=api)

    return jsonify(status='success', data=dict(message='Node deleted'))


@blueprint.route('/e/toggle-node-public', methods=['POST'])
@login_required
def toggle_node_public():
    """Give a node GET permissions for the world. Later on this can turn into
    a more powerful permission management function.
    """
    api = system_util.pillar_api()
    node = Node.find(request.form['node_id'], api=api)
    if node.has_method('PUT'):
        if node.permissions and 'world' in node.permissions.to_dict():
            node.permissions = {}
            message = "Node is not public anymore."
        else:
            node.permissions = dict(world=['GET'])
            message = "Node is now public!"
        node.update(api=api)
        return jsonify(status='success', data=dict(message=message))
    else:
        return abort(403)


@blueprint.route('/e/toggle-node-project-header', methods=['POST'])
@login_required
def toggle_node_project_header():
    """Sets this node as the project header, or removes it if already there.
    """

    api = system_util.pillar_api()
    node_id = request.form['node_id']

    try:
        node = Node.find(node_id, {'projection': {'project': 1}}, api=api)
    except ResourceNotFound:
        log.info('User %s trying to toggle non-existing node %s as project header',
                 current_user.objectid, node_id)
        return jsonify(_status='ERROR', message='Node not found'), 404

    try:
        project = Project.find(node.project, api=api)
    except ResourceNotFound:
        log.info('User %s trying to toggle node %s as project header, but project %s not found',
                 current_user.objectid, node_id, node.project)
        return jsonify(_status='ERROR', message='Project not found'), 404

    # Toggle header node
    if project.header_node == node_id:
        log.debug('Un-setting header node of project %s', node.project)
        project.header_node = None
        action = 'unset'
    else:
        log.debug('Setting node %s as header of project %s', node_id, node.project)
        project.header_node = node_id
        action = 'set'

    # Save the project
    project.update(api=api)

    return jsonify({'_status': 'OK',
                    'action': action})


def project_update_nodes_list(node, project_id=None, list_name='latest'):
    """Update the project node with the latest edited or favorited node.
    The list value can be 'latest' or 'featured' and it will determined where
    the node reference will be placed in.
    """
    if node.properties.status and node.properties.status == 'published':
        if not project_id and 'current_project_id' in session:
            project_id = session['current_project_id']
        elif not project_id:
            return None
        project_id = node.project
        if type(project_id) is not str:
            project_id = node.project._id
        api = system_util.pillar_api()
        project = Project.find(project_id, api=api)
        if list_name == 'latest':
            nodes_list = project.nodes_latest
        elif list_name == 'blog':
            nodes_list = project.nodes_blog
        else:
            nodes_list = project.nodes_featured

        if not nodes_list:
            node_list_name = 'nodes_' + list_name
            project[node_list_name] = []
            nodes_list = project[node_list_name]
        elif len(nodes_list) > 15:
            nodes_list.pop(0)

        if node._id in nodes_list:
            # Pop to put this back on top of the list
            nodes_list.remove(node._id)
            if list_name == 'featured':
                # We treat the action as a toggle and do not att the item back
                project.update(api=api)
                return "removed"

        nodes_list.append(node._id)
        project.update(api=api)
        return "added"


@blueprint.route('/create')
@login_required
def create():
    """Create a new project. This is a multi step operation that involves:
    - initialize basic node types
    - initialize basic permissions
    - create and connect storage space
    """
    api = system_util.pillar_api()
    project_properties = dict(
        name='My project',
        user=current_user.objectid,
        category='assets',
        status='pending'
    )
    project = Project(project_properties)
    project.create(api=api)

    return redirect(url_for('projects.edit',
                            project_url="p-{}".format(project['_id'])))


@blueprint.route('/delete', methods=['POST'])
@login_required
def delete():
    """Unapologetically deletes a project"""
    api = system_util.pillar_api()
    project_id = request.form['project_id']
    project = Project.find(project_id, api=api)
    project.delete(api=api)
    return jsonify(dict(staus='success', data=dict(
        message='Project deleted {}'.format(project['_id']))))


@blueprint.route('/<project_url>/edit/<extension_name>', methods=['GET', 'POST'])
@login_required
@project_view()
def edit_extension(project: Project, extension_name):
    try:
        ext = current_app.pillar_extensions[extension_name]
    except KeyError:
        raise wz_exceptions.NotFound()

    return ext.project_settings(project,
                                ext_pages=find_extension_pages())
