package com.nateyolles.sling.publick.services.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.jackrabbit.JcrConstants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.jcr.resource.JcrResourceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.nateyolles.sling.publick.PublickConstants;
import com.nateyolles.sling.publick.services.AkismetService;
import com.nateyolles.sling.publick.services.CommentService;

/**
 * Service to get, save, and update comments as well as mark them as
 * spam or ham (valid).
 */
@Service(value = CommentService.class)
@Component(immediate = true,
           name = "Publick comments service",
           description = "Service to get, delete, update and add comments.")
public class CommentServiceImpl implements CommentService {

    /** Akismet service to mark comments as spam or ham. */
    @Reference
    private AkismetService akismetService;

    /**
     * The logger.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(CommentServiceImpl.class);

    /**
     * JCR_SQL2 query to get all comments that are not hidden in order of newest first.
     */
    private static final String BLOG_QUERY = String.format("SELECT * FROM [%s] AS s WHERE "
            + "ISDESCENDANTNODE([%s]) AND s.[%s] = '%s' ORDER BY [%s] desc",
            PublickConstants.NODE_TYPE_COMMENT,
            PublickConstants.COMMENTS_PATH,
            PublickConstants.COMMENT_PROPERTY_DISPLAY,
            true,
            JcrConstants.JCR_CREATED);

    /**
     * Get all Comments in the order of newest to oldest.
     *
     * @param request The current request
     * @return List of all comments
     */
    public List<Resource> getComments(final SlingHttpServletRequest request) {
        final List<Resource> comments = new ArrayList<>();
        final Iterator<Resource> queryResults = request.getResourceResolver().findResources(BLOG_QUERY, Query.JCR_SQL2);

        while (queryResults.hasNext()) {
            comments.add(queryResults.next());
        }

        return comments;
    }

    /**
     * Delete comment by setting it's display property to false.
     *
     * @param request The current request to get session and Resource Resolver
     * @param id The comment UUID
     * @return true if the operation was successful
     */
    public boolean deleteComment(final SlingHttpServletRequest request, final String id) {
        boolean result = false;

        try {
            Session session = request.getResourceResolver().adaptTo(Session.class); 
            Node node = session.getNodeByIdentifier(id);

            if (node != null) {
                JcrResourceUtil.setProperty(node, PublickConstants.COMMENT_PROPERTY_DISPLAY, false);
                session.save();
                result = true;
            }
        } catch (RepositoryException e) {
            LOGGER.error("Could not delete comment from JCR", e);
        }

        return result;
    }

    /**
     * Edit comment and mark it as author edited.
     *
     * @param request The current request to get session and Resource Resolver
     * @param id The comment UUID
     * @param text The comment text
     * @return true if the operation was successful
     */
    public boolean editComment(final SlingHttpServletRequest request, final String id, String text) {
        boolean result = false;

        try {
            Session session = request.getResourceResolver().adaptTo(Session.class);
            Node node = session.getNodeByIdentifier(id);

            if (node != null) {
                JcrResourceUtil.setProperty(node, PublickConstants.COMMENT_PROPERTY_COMMENT, text);
                JcrResourceUtil.setProperty(node, PublickConstants.COMMENT_PROPERTY_EDITED, true);
                session.save();
                result = true;
            }
        } catch (RepositoryException e) {
            LOGGER.error("Could not update comment from JCR", e);
        }

        return result;
    }

    /**
     * Mark comment as spam, submit it to Akismet and delete it by setting
     * it's display property to false.
     *
     * @param request The current request to get session and Resource Resolver
     * @param id The comment UUID
     * @return true if the operation was successful
     */
    public boolean markAsSpam(final SlingHttpServletRequest request, final String id) {
        boolean result = false;

        try {
            final ResourceResolver resolver = request.getResourceResolver();
            final Session session = resolver.adaptTo(Session.class);
            final Node node = session.getNodeByIdentifier(id);

            if (node != null) {
                final Resource resource = resolver.getResource(node.getPath());
                result = akismetService.submitSpam(resource);
            }
        } catch (RepositoryException e) {
            LOGGER.error("Could not submit spam.", e);
        }

        return result;
    }

    /**
     * Mark comment as ham, submit it to Akismet and mark it valid it by setting
     * it's display property to true.
     *
     * @param request The current request to get session and Resource Resolver
     * @param id The comment UUID
     * @return true if the operation was successful
     */
    public boolean markAsHam(final SlingHttpServletRequest request, final String id) {
        boolean result = false;

        try {
            final ResourceResolver resolver = request.getResourceResolver();
            final Session session = resolver.adaptTo(Session.class);
            final Node node = session.getNodeByIdentifier(id);

            if (node != null) {
                final Resource resource = resolver.getResource(node.getPath());
                result = akismetService.submitHam(resource);
            }
        } catch (RepositoryException e) {
            LOGGER.error("Could not submit ham.", e);
        }

        return result;
    }

    /**
     * Get the number of replies for a given comment.
     *
     * @param comment The current comment
     * @return The number of replies for the comment
     */
    public int numberOfReplies(final Resource comment) {
        final Iterator<Resource> children = comment.listChildren();
        int size = 0;

        while (children.hasNext()) {
            children.next();
            size++;
        }

        return size;
    }

    /**
     * Get the blog post associated with the given comment.
     *
     * There are only two levels of comments. You can reply to a post
     * and you can reply to a top level comment.
     *
     * @param comment The current comment
     * @return the number of replies to the given comment
     */
    public Resource getParentPost(final Resource comment) {
        final ResourceResolver resolver = comment.getResourceResolver();
        Resource parent = comment.getParent();

        // Try one level up
        Resource post = resolver.getResource(parent.getPath().replace("/comments/", "/blog/"));

        if (post == null) {
            //try two levels up
            parent = parent.getParent();
            post = resolver.getResource(parent.getPath().replace("/comments/", "/blog/"));
        }

        return post;
    }
}