import functools
import logging

from pillarsdk import Node
from pillarsdk import Project
from pillarsdk.exceptions import ResourceNotFound
from flask import abort, url_for
from flask import current_app
from flask import render_template
from flask import redirect
from flask_login import login_required, current_user
from pillar.web.utils import system_util
from pillar.web.utils import attach_project_pictures
from pillar.web.utils import get_file
from pillar.web.utils import current_user_is_authenticated

from pillar.web.nodes.routes import blueprint
from pillar.web.nodes.routes import url_for_node
from pillar.web.nodes.forms import get_node_form
import pillar.web.nodes.attachments
from pillar.web.projects.routes import project_update_nodes_list
from pillar.web.projects.routes import project_navigation_links

log = logging.getLogger(__name__)


# Cached, see setup_app() below.
def posts_view(project_id=None, project_url=None, url=None, *, archive=False, page=1):
    """View individual blogpost"""

    if bool(project_id) == bool(project_url):
        raise ValueError('posts_view(): pass either project_id or project_url')

    if url and archive:
        raise ValueError('posts_view(): cannot pass both url and archive')

    api = system_util.pillar_api()

    # Fetch project (for background images and links generation)
    if project_id:
        project = Project.find(project_id, api=api)
    else:
        project = Project.find_one({'where': {'url': project_url}}, api=api)
        project_id = project['_id']

    attach_project_pictures(project, api)

    blog = Node.find_one({
        'where': {'node_type': 'blog', 'project': project_id},
    }, api=api)

    status_query = {} if blog.has_method('PUT') else {'properties.status': 'published'}
    posts = Node.all({
        'where': {'parent': blog._id, **status_query},
        'embedded': {'user': 1},
        'sort': '-_created',
        'max_results': 20 if archive else 5,
        'page': page,
    }, api=api)

    for post in posts._items:
        post.picture = get_file(post.picture, api=api)
        post.url = url_for_node(node=post)

    index_arch = 'archive' if archive else 'index'
    template_path = f'nodes/custom/blog/{index_arch}.html',

    if url:
        post = Node.find_one({
            'where': {'parent': blog._id, 'properties.url': url},
            'embedded': {'node_type': 1, 'user': 1},
        }, api=api)

        if post.picture:
            post.picture = get_file(post.picture, api=api)

        post.url = url_for_node(node=post)
    elif posts._items:
        post = posts._items[0]
    else:
        post = None

    if post is not None:
        # If post is not published, check that the user is also the author of
        # the post. If not, return an error.
        if post.properties.status != "published":
            if not (current_user.is_authenticated and post.has_method('PUT')):
                abort(403)

    can_create_blog_posts = project.node_type_has_method('post', 'POST', api=api)

    # Use functools.partial so we can later pass page=X.
    is_main_project = project_id == current_app.config['MAIN_PROJECT_ID']
    if is_main_project:
        url_func = functools.partial(url_for, 'main.main_blog_archive')
    else:
        url_func = functools.partial(url_for, 'main.project_blog_archive', project_url=project.url)

    project.blog_archive_url = url_func()
    pmeta = posts._meta
    seen_now = pmeta['max_results'] * pmeta['page']
    if pmeta['total'] > seen_now:
        project.blog_archive_next = url_func(page=pmeta['page'] + 1)
    else:
        project.blog_archive_next = None
    if pmeta['page'] > 1:
        project.blog_archive_prev = url_func(page=pmeta['page'] - 1)
    else:
        project.blog_archive_prev = None

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

    return render_template(
        template_path,
        blog=blog,
        node=post,  # node is used by the generic comments rendering (see custom/_scripts.pug)
        posts=posts._items,
        posts_meta=pmeta,
        more_posts_available=pmeta['total'] > pmeta['max_results'],
        project=project,
        node_type_post=project.get_node_type('post'),
        can_create_blog_posts=can_create_blog_posts,
        navigation_links=navigation_links,
        extension_sidebar_links=extension_sidebar_links,
        api=api)


@blueprint.route("/posts/<project_id>/create", methods=['GET', 'POST'])
@login_required
def posts_create(project_id):
    api = system_util.pillar_api()
    try:
        project = Project.find(project_id, api=api)
    except ResourceNotFound:
        return abort(404)
    attach_project_pictures(project, api)

    blog = Node.find_one({
        'where': {'node_type': 'blog', 'project': project_id}}, api=api)
    node_type = project.get_node_type('post')
    # Check if user is allowed to create a post in the blog
    if not project.node_type_has_method('post', 'POST', api=api):
        return abort(403)
    form = get_node_form(node_type)
    if form.validate_on_submit():
        # Create new post object from scratch
        post_props = dict(
            node_type='post',
            name=form.name.data,
            picture=form.picture.data,
            user=current_user.objectid,
            parent=blog._id,
            project=project._id,
            properties=dict(
                content=form.content.data,
                status=form.status.data,
                url=form.url.data))
        if form.picture.data == '':
            post_props['picture'] = None
        post = Node(post_props)
        post.create(api=api)
        # Only if the node is set as published, push it to the list
        if post.properties.status == 'published':
            project_update_nodes_list(post, project_id=project._id, list_name='blog')
        return redirect(url_for_node(node=post))
    form.parent.data = blog._id
    return render_template('nodes/custom/post/create.html',
                           node_type=node_type,
                           form=form,
                           project=project,
                           api=api)


def setup_app(app):
    global posts_view

    memoize = app.cache.memoize(timeout=3600, unless=current_user_is_authenticated)
    posts_view = memoize(posts_view)
