/**
 * 
 */
package de.bstly.board.businesslogic;

import java.time.Instant;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.stereotype.Component;

import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;

import de.bstly.board.model.Comment;
import de.bstly.board.model.QComment;
import de.bstly.board.model.QEntry;
import de.bstly.board.model.QFlag;
import de.bstly.board.model.QVote;
import de.bstly.board.model.support.FlaggedStatus;
import de.bstly.board.model.support.Types;
import de.bstly.board.model.support.VoteType;
import de.bstly.board.repository.CommentRepository;
import de.bstly.board.repository.VoteRepository;

/**
 * The Class CommentManager.
 */
@Component
public class CommentManager {

	@Autowired
	private CommentRepository commentRepository;
	@Autowired
	private JPAQueryFactory jpaQueryFactory;
	@Autowired
	private VoteRepository voteRepository;
	@Autowired
	private VoteManager voteManager;
	@Autowired
	private FlagManager flagManager;

	private QComment qComment = QComment.comment;
	private QVote qVote = QVote.vote;
	private QFlag qFlag = QFlag.flag;
	private QEntry qEntry = QEntry.entry;

	/**
	 * Fetch by date.
	 *
	 * @param username the username
	 * @param target the target
	 * @param parent the parent
	 * @param date the date
	 * @param page the page
	 * @param size the size
	 * @param desc the desc
	 * @return the page
	 */
	public Page<Comment> fetchByDate(String username, Long target, Long parent, Instant date,
			int page, int size, boolean desc) {
		Sort sort = Sort.by(desc ? Order.desc("created") : Order.asc("created"));
		if (parent == null) {
			return commentRepository
					.findAll(
							qComment.target.eq(target).and(qComment.parent.isNull())
									.and(qComment.flaggedStatus.eq(FlaggedStatus.NORMAL))
									.and(qComment.created.before(date)
											.or(qComment.author.eq(username))),
							PageRequest.of(page, size, sort));
		}

		return commentRepository.findAll(qComment.target.eq(target).and(qComment.parent.eq(parent))
				.and(qComment.flaggedStatus.eq(FlaggedStatus.NORMAL))
				.and(qComment.created.before(date)), PageRequest.of(page, size, sort));
	}

	/**
	 * Fetch flagged.
	 *
	 * @param page the page
	 * @param size the size
	 * @param asc the asc
	 * @return the page
	 */
	public Page<Comment> fetchFlagged(int page, int size, boolean asc) {
		Sort sort = Sort.by(asc ? Order.asc("created") : Order.desc("created"));
		JPAQuery<Comment> query = jpaQueryFactory.selectFrom(qComment).leftJoin(qFlag)
				.on(qComment.id.eq(qFlag.target)).where(qFlag.targetType.eq(Types.comment))
				.groupBy(qFlag.target);
		return new PageImpl<Comment>(query
				.orderBy(new OrderSpecifier<>(asc ? com.querydsl.core.types.Order.ASC
						: com.querydsl.core.types.Order.DESC, qComment.created))
				.limit(size).offset(page * size).fetch(), PageRequest.of(page, size, sort),
				query.fetchCount());
	}

	/**
	 * Fetch by username.
	 *
	 * @param username the username
	 * @param date the date
	 * @param page the page
	 * @param size the size
	 * @param asc the asc
	 * @return the page
	 */
	public Page<Comment> fetchByUsername(String username, Instant date, int page, int size,
			boolean asc) {
		Sort sort = Sort.by(asc ? Order.asc("created") : Order.desc("created"));
		return commentRepository.findAll(qComment.author.equalsIgnoreCase(username)
				.and(qComment.flaggedStatus.eq(FlaggedStatus.NORMAL))
				.and(qComment.created.before(date)), PageRequest.of(page, size, sort));
	}

	/**
	 * Count.
	 *
	 * @param target the target
	 * @param parent the parent
	 * @return the long
	 */
	public Long count(Long target, Long parent) {
		if (parent == null) {
			return count(target);
		}

		return commentRepository.count(qComment.target.eq(target).and(qComment.parent.eq(parent))
				.and(qComment.flaggedStatus.eq(FlaggedStatus.NORMAL))
				.and(qComment.created.before(Instant.now())));
	}

	/**
	 * Count.
	 *
	 * @param target the target
	 * @return the long
	 */
	public Long count(Long target) {
		return commentRepository.count(
				qComment.target.eq(target).and(qComment.flaggedStatus.eq(FlaggedStatus.NORMAL))
						.and(qComment.created.before(Instant.now())));
	}

	/**
	 * Apply metadata.
	 *
	 * @param username the username
	 * @param comment the comment
	 * @param ignore the ignore
	 */
	public void applyMetadata(String username, Comment comment, List<String> ignore) {

		ignore.addAll(comment.getMetadata().keySet());

		if (!ignore.contains("author")) {
			comment.getMetadata().put("author", comment.getAuthor());
		}

		if (!ignore.contains("comments")) {
			comment.getMetadata().put("comments", count(comment.getTarget(), comment.getId()));
		}

		if (!ignore.contains("points")) {
			comment.getMetadata().put("points",
					voteManager.getPoints(comment.getId(), Types.comment));
		}

		if (!ignore.contains("upvoted")) {
			comment.getMetadata().put("upvoted",
					voteRepository.exists(qVote.target.eq(comment.getId())
							.and(qVote.targetType.eq(Types.comment)).and(qVote.type.eq(VoteType.up))
							.and(qVote.author.equalsIgnoreCase(username))));
		}

		if (!ignore.contains("downvoted")) {
			comment.getMetadata().put("downvoted",
					voteRepository.exists(
							qVote.target.eq(comment.getId()).and(qVote.targetType.eq(Types.comment))
									.and(qVote.type.eq(VoteType.down))
									.and(qVote.author.equalsIgnoreCase(username))));
		}

		if (!ignore.contains("unvote")) {
			comment.getMetadata().put("unvote",
					voteRepository.exists(
							qVote.target.eq(comment.getId()).and(qVote.targetType.eq(Types.comment))
									.and(qVote.author.equalsIgnoreCase(username))));
		}

		if (!username.equals(comment.getAuthor()) && !ignore.contains("flag")) {
			comment.getMetadata().put("flag",
					flagManager.get(username, comment.getId(), Types.comment) == null);
		}

		if (!username.equals(comment.getAuthor()) && !ignore.contains("unflag")) {
			comment.getMetadata().put("unflag",
					flagManager.get(username, comment.getId(), Types.comment) != null);
		}

		if (!ignore.contains("flagged")) {
			comment.getMetadata().put("flagged",
					flagManager.getFlags(comment.getId(), Types.comment) > 0);
		}

		if (!ignore.contains("entry")) {
			comment.getMetadata().put("entry", jpaQueryFactory.selectFrom(qEntry)
					.where(qEntry.id.eq(comment.getTarget())).select(qEntry.title).fetchOne());
		}
	}

	/**
	 * Apply metadata.
	 *
	 * @param username the username
	 * @param entries the entries
	 * @param ignore the ignore
	 */
	public void applyMetadata(String username, List<Comment> entries, List<String> ignore) {
		for (Comment comment : entries) {
			applyMetadata(username, comment, ignore);
		}
	}

	/**
	 * Exists.
	 *
	 * @param id the id
	 * @return true, if successful
	 */
	public boolean exists(Long id) {
		return commentRepository.existsById(id);
	}

	/**
	 * Gets the.
	 *
	 * @param id the id
	 * @return the comment
	 */
	public Comment get(Long id) {
		return commentRepository.findById(id).orElse(null);
	}

	/**
	 * Save.
	 *
	 * @param comment the comment
	 * @return the comment
	 */
	public Comment save(Comment comment) {
		return commentRepository.save(comment);
	}

	/**
	 * Gets the points.
	 *
	 * @param commentId the comment id
	 * @return the points
	 */
	public long getPoints(Long commentId) {
		long upvotes = voteRepository.count(qVote.targetType.eq(Types.comment)
				.and(qVote.type.eq(VoteType.up)).and(qVote.target.eq(commentId)));
		long downvotes = voteRepository.count(qVote.targetType.eq(Types.comment)
				.and(qVote.type.eq(VoteType.down)).and(qVote.target.eq(commentId)));
		return upvotes - downvotes;
	}

	/**
	 * Delete.
	 *
	 * @param comment the comment
	 */
	public void delete(Comment comment) {
		for (Comment subcomment : commentRepository.findAll(qComment.parent.eq(comment.getId()))) {
			delete(subcomment);
		}

		voteManager.deleteByTarget(comment.getId(), Types.comment);
		flagManager.deleteByTarget(comment.getId(), Types.comment);

		commentRepository.delete(comment);
	}

	/**
	 * Delete by target.
	 *
	 * @param target the target
	 */
	public void deleteByTarget(Long target) {
		for (Comment comment : commentRepository.findAll(qComment.target.eq(target))) {
			delete(comment);
		}
	}

}
