package com.apobates.forum.core.impl.dao;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.apobates.forum.core.api.dao.PostsDao;
import com.apobates.forum.core.entity.Board;
import com.apobates.forum.core.entity.BoardGroup;
import com.apobates.forum.core.entity.Posts;
import com.apobates.forum.core.entity.Topic;
import com.apobates.forum.utils.persistence.Page;
import com.apobates.forum.utils.persistence.Pageable;

@Repository
public class PostsDaoImpl implements PostsDao {
	@PersistenceContext
	private EntityManager entityManager;
	private final static Logger logger = LoggerFactory.getLogger(PostsDaoImpl.class);

	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public void save(Posts entity) {
		if(entity.getFloorNumber() == -1){
			entity.setFloorNumber(maxFloor(entity.getTopicId())+1);
		}
		entityManager.persist(entity);
	}

	@Override
	public Optional<Posts> findOne(Long primaryKey) {
		return Optional.ofNullable(entityManager.find(Posts.class, primaryKey));
	}
	
	@Override
	public Optional<Boolean> edit(Posts updateEntity) {
		return Optional.empty();
	}

	@Override
	public Stream<Posts> findAll() {
		return Stream.empty();
	}

	@Override
	public long count() {
		return -1L;
	}

	@Override
	public Page<Posts> findAll(Pageable pageable) {
		return emptyResult();
	}
	
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Optional<Boolean> editStatus(long id, boolean status) {
		try {
			int affect = entityManager.createQuery("UPDATE Posts p SET p.status = ?1 WHERE p.id = ?2").setParameter(1, status).setParameter(2, id).executeUpdate();
			return (affect == 1)?Optional.of(true):Optional.empty();
		}catch(Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.empty();
	}

	@Override
	public Page<Posts> findAllByTopic(long topicId, Pageable pageable) {
		final long total = findAllByTopicCount(topicId);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<Posts> query = entityManager.createQuery("SELECT p FROM Posts p WHERE p.topicId = ?1 ORDER BY p.id ASC", Posts.class).setParameter(1, topicId);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

		final Stream<Posts> result = query.getResultStream();
		return new Page<Posts>() {
			@Override
			public long getTotalElements() {
				return total;
			}

			@Override
			public Stream<Posts> getResult() {
				return result;
			}
		};
	}

	private long findAllByTopicCount(long topicId) {
		try {
			return entityManager.createQuery("SELECT COUNT(p) FROM Posts p WHERE p.topicId = ?1", Long.class).setParameter(1, topicId).getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findAllByTopicCount][PostsDao]", e);
			}
		}
		return 0L;
	}

	@Override
	public Page<Posts> findAllReplyByTopic(long topicId, Pageable pageable) {
		final long total = countTopicReply(topicId);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<Posts> query = entityManager.createQuery("SELECT p FROM Posts p WHERE p.topicId = ?1 AND p.reply = ?2 ORDER BY p.id ASC", Posts.class).setParameter(1, topicId).setParameter(2, true);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

		final Stream<Posts> result = query.getResultStream();
		return new Page<Posts>() {
			@Override
			public long getTotalElements() {
				return total;
			}

			@Override
			public Stream<Posts> getResult() {
				return result;
			}
		};
	}
	
	@Override
	public long countTopicReply(long topicId) {
		try {
			return entityManager.createQuery("SELECT COUNT(p) FROM Posts p WHERE p.topicId = ?1 AND p.reply = ?2", Long.class).setParameter(1, topicId).setParameter(2, true).getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[countTopicReply][PostsDao]", e);
			}
		}
		return 0L;
	}
	
	@Override
	public Page<Posts> findAllReplyByTopic(long topicId, long filterMemeberId, Pageable pageable) {
		final long total = findAllReplyByTopicCount(topicId, filterMemeberId);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<Posts> query = entityManager.createQuery("SELECT p FROM Posts p WHERE p.topicId = ?1 AND p.reply = ?2 AND p.memberId = ?3 ORDER BY p.id ASC", Posts.class)
				.setParameter(1, topicId)
				.setParameter(2, true)
				.setParameter(3, filterMemeberId);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

		final Stream<Posts> result = query.getResultStream();
		return new Page<Posts>() {
			@Override
			public long getTotalElements() {
				return total;
			}

			@Override
			public Stream<Posts> getResult() {
				return result;
			}
		};
	}
	
	private long findAllReplyByTopicCount(long topicId, long filterMemeberId){
		try {
			return entityManager.createQuery("SELECT COUNT(p) FROM Posts p WHERE p.topicId = ?1 AND p.reply = ?2 AND p.memberId = ?3", Long.class)
					.setParameter(1, topicId)
					.setParameter(2, true)
					.setParameter(3, filterMemeberId)
					.getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findAllReplyByTopicCount][PostsDao]", e);
			}
		}
		return 0L;
	}
	
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Optional<Boolean> edit(long id, String content,long editMemberId, String editMemberNickname) {
		try {
			int affect = entityManager.createQuery("UPDATE Posts p SET p.content = ?1, p.modifyMemberId = ?2, p.modifyMemberNickname = ?3, p.modifyDateTime = ?4 WHERE p.id = ?5 AND p.reply = ?6")
					.setParameter(1, content)
					.setParameter(2, editMemberId)
					.setParameter(3, editMemberNickname)
					.setParameter(4, LocalDateTime.now())
					.setParameter(5, id)
					.setParameter(6, true)
					.executeUpdate();
			return (affect == 1)?Optional.of(true):Optional.empty();
		}catch(Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.empty();
	}
	
	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public Optional<Boolean> editTopicContent(long topicId, String content, long editMemberId, String editMemberNickname) {
		try {
			int affect = entityManager.createQuery("UPDATE Posts p SET p.content = ?1, p.modifyMemberId = ?2, p.modifyMemberNickname = ?3, p.modifyDateTime = ?4 WHERE p.topicId = ?5 AND p.reply = ?6")
					.setParameter(1, content)
					.setParameter(2, editMemberId)
					.setParameter(3, editMemberNickname)
					.setParameter(4, LocalDateTime.now())
					.setParameter(5, topicId)
					.setParameter(6, false)
					.executeUpdate();
			return (affect == 1)?Optional.of(true):Optional.empty();
		}catch(Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.empty();
	}

	@Override
	public Optional<Posts> findOneByOneFloor(long topicId) {
		try {
			Posts p = entityManager.createQuery("SELECT p FROM Posts p WHERE p.topicId = ?1 AND p.reply = ?2", Posts.class).setParameter(1, topicId).setParameter(2, false).getSingleResult();
			if(null!=p){
				Topic t = entityManager.find(Topic.class, topicId);
				if(null!=t){
					p.setTopic(t);
				}
			}
			return Optional.ofNullable(p);
		} catch (javax.persistence.NoResultException e) {
			if (logger.isDebugEnabled()) {
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.empty();
	}

	@Override
	public long countRepliesSize(long topicId, long memberId) {
		try{
		return entityManager.createQuery("SELECT COUNT(p) FROM Posts p WHERE p.topicId = ?1 AND p.reply = ?2 AND p.memberId = ?3", Long.class)
				.setParameter(1, topicId)
				.setParameter(2, true)
				.setParameter(3, memberId)
				.getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[countRepliesSize][PostsDao]", e);
			}
		}
		return 0L;
	}

	@Override
	public Posts findOneForIndex(long id) {
		Posts p = entityManager.find(Posts.class, id);
		if(p == null) {
			return null;
		}
		if(p.getTopicId()>0) {
			try {
				Topic t = entityManager.createQuery("SELECT t FROM Topic t WHERE t.id = ?1", Topic.class).setParameter(1, p.getTopicId()).getSingleResult();
				if(t!=null) {
					p.setTopic(t);
				}
			}catch(javax.persistence.NoResultException e) {}
		}
		if(p.getBoardId()>0) {
			try {
				Board b = entityManager.createQuery("SELECT b FROM Board b WHERE b.id = ?1", Board.class).setParameter(1, p.getBoardId()).getSingleResult();
				if(b!=null) {
					p.setBoard(b);
				}
			}catch(javax.persistence.NoResultException e) {}
		}
		if(p.getVolumesId()>0) {
			try {
				BoardGroup bg = entityManager.createQuery("SELECT bg FROM BoardGroup bg WHERE bg.id = ?1", BoardGroup.class).setParameter(1, p.getVolumesId()).getSingleResult();
				if(bg!=null) {
					p.setVolumes(bg);
				}
			}catch(javax.persistence.NoResultException e) {}
		}else {
			p.setVolumes(BoardGroup.getDefault());
		}
		return p;
	}

	@Override
	public Stream<Posts> findAllRecentByTopic(long topicId, LocalDateTime prevDate) {
		return entityManager.createQuery("SELECT p FROM Posts p WHERE p.topicId = ?1 AND p.entryDateTime BETWEEN ?2 AND ?3 AND p.reply = ?4 ORDER BY p.id ASC", Posts.class)
				.setParameter(1, topicId)
				.setParameter(2, prevDate)
				.setParameter(3, LocalDateTime.now())
				.setParameter(4, true)
				.getResultStream();
	}

	@Override
	public Stream<Posts> findAllRecentByTopicIgnoreStatus(long topicId, int size) {
		return entityManager.createQuery("SELECT p FROM Posts p WHERE p.topicId = ?1 ORDER BY p.entryDateTime DESC", Posts.class)
				.setParameter(1, topicId)
				.setMaxResults(size)
				.getResultStream();
	}

	@Override
	public Posts findOneRelativeTopic(long id) {
		Posts p = entityManager.find(Posts.class, id);
		if(p == null) {
			return null;
		}
		if(p.getTopicId()>0) {
			try {
				Topic t = entityManager.createQuery("SELECT t FROM Topic t WHERE t.id = ?1", Topic.class).setParameter(1, p.getTopicId()).getSingleResult();
				if(t!=null) {
					p.setTopic(t);
				}
			}catch(javax.persistence.NoResultException e) {
				if(logger.isDebugEnabled()){
					logger.debug("[PostsDao]回复加载话题失败", e);
				}
			}
		}
		return p;
	}

	@Override
	public Stream<Posts> findOneByOneFloor(List<Long> topicIdList) {
		if(topicIdList == null || topicIdList.isEmpty()){
			return Stream.empty();
		}
		return entityManager.createQuery("SELECT p FROM Posts p WHERE p.topicId IN ?1 AND p.reply = ?2", Posts.class).setParameter(1, topicIdList).setParameter(2, false).getResultStream();
	}

	@Override
	public Stream<Posts> findAllByTopic(long topicId, int size) {
		return entityManager
				.createQuery("SELECT p FROM Posts p WHERE p.topicId = ?1 AND p.reply = ?2 ORDER BY p.entryDateTime DESC", Posts.class)
				.setParameter(1, topicId)
				.setParameter(2, true)
				.setMaxResults(size)
				.getResultStream();
	}

	@Override
	public Stream<Posts> findAllByTopic(long topicId, int page, int size) {
		TypedQuery<Posts> query = entityManager.createQuery("SELECT p FROM Posts p WHERE p.topicId = ?1 AND p.reply = ?2 ORDER BY p.floorNumber ASC", Posts.class)
				.setParameter(1, topicId)
				.setParameter(2, true);
		query.setFirstResult((page-1) * size);
		query.setMaxResults(size);
		return query.getResultStream();
	}

	@Override
	public long maxFloor(long topicId) {
		try{
			return entityManager.createQuery("SELECT MAX(p.floorNumber) FROM Posts p WHERE p.topicId = ?1", Long.class).setParameter(1, topicId).getSingleResult();
		}catch(javax.persistence.NoResultException e){
			if(logger.isDebugEnabled()){
				logger.debug("[PostsDao]获取话题的楼层失败", e);
			}
		}
		return 1L;
	}

	@Override
	public Stream<Posts> findAll(Set<Long> postsIdList) {
		if(postsIdList == null || postsIdList.isEmpty()){
			return Stream.empty();
		}
		return entityManager
				.createQuery("SELECT p FROM Posts p WHERE p.id IN ?1", Posts.class)
				.setParameter(1, postsIdList)
				.getResultStream();
	}
}
