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

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
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.TopicActionCollectionDao;
import com.apobates.forum.core.entity.TopicActionCollection;
import com.apobates.forum.event.elderly.ForumActionEnum;
import com.apobates.forum.utils.DateTimeUtils;
import com.apobates.forum.utils.persistence.Page;
import com.apobates.forum.utils.persistence.Pageable;

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

	@Override
	public Page<TopicActionCollection> findAll(Pageable pageable) {
		final long total = count();
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<TopicActionCollection> query = entityManager.createQuery("SELECT tac FROM TopicActionCollection tac ORDER BY tac.entryDateTime DESC", TopicActionCollection.class);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

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

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

	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public void save(TopicActionCollection entity) {
		EnumSet<ForumActionEnum> oneRecorded = EnumSet.of(
				ForumActionEnum.COMMON_VISIT, 
				ForumActionEnum.TOPIC_BROWSE, 
				ForumActionEnum.COMMON_CASUAL, 
				ForumActionEnum.BOARD_HOME, 
				ForumActionEnum.BOARD_BROWSE, 
				ForumActionEnum.BOARD_GROUP_BROWSE, 
				ForumActionEnum.MESSAGE_BROWSE);
		// 一个会员一天浏览只记录一次
		if (oneRecorded.contains(entity.getAction()) && entity.getMemberId() > 0) {
			entityManager.createQuery("DELETE FROM TopicActionCollection tc WHERE tc.topicId = ?1 AND tc.memberId = ?2 AND tc.action = ?3 AND tc.entryDateTime BETWEEN ?4 AND ?5")
					.setParameter(1, entity.getTopicId())
					.setParameter(2, entity.getMemberId())
					.setParameter(3, entity.getAction())
					.setParameter(4, DateTimeUtils.getTodayEarlyMorning())
					.setParameter(5, DateTimeUtils.getTodayMidnight())
					.executeUpdate();
		}
		entityManager.persist(entity);
	}

	@Override
	public Optional<TopicActionCollection> findOne(Long primaryKey) {
		return Optional.ofNullable(entityManager.find(TopicActionCollection.class, primaryKey));
	}

	@Override
	public Optional<Boolean> edit(TopicActionCollection updateEntity) {
		return Optional.empty();
	}

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

	@Override
	public long count() {
		try {
			return entityManager.createQuery("SELECT COUNT(tac) FROM TopicActionCollection tac", Long.class).getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[count][TopicActionCollectionDao]", e);
			}
		}
		return 0L;
	}

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

	@Override
	public Page<TopicActionCollection> findByMember(long memberId, Pageable pageable) {
		final long total = findByMemberCount(memberId);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<TopicActionCollection> query = entityManager.createQuery("SELECT tac FROM TopicActionCollection tac WHERE tac.memberId = ?1 ORDER BY tac.entryDateTime DESC", TopicActionCollection.class).setParameter(1, memberId);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

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

			@Override
			public Stream<TopicActionCollection> getResult() {
				return result;
			}
		};
	}
	@Override
	public long findByMemberCount(long memberId) {
		try {
			return entityManager.createQuery("SELECT COUNT(tac) FROM TopicActionCollection tac WHERE tac.memberId = ?1", Long.class)
					.setParameter(1, memberId)
					.getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findByMemberCount][TopicActionCollectionDao]", e);
			}
		}
		return 0L;
	}

	@Override
	public Page<TopicActionCollection> findAllByAction(ForumActionEnum action, Pageable pageable) {
		final long total = findAllByActionCount(action);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<TopicActionCollection> query = entityManager.createQuery("SELECT tac FROM TopicActionCollection tac WHERE tac.action = ?1 ORDER BY tac.entryDateTime DESC", TopicActionCollection.class).setParameter(1, action);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

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

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

	private long findAllByActionCount(ForumActionEnum action) {
		try {
			return entityManager.createQuery("SELECT COUNT(tac) FROM TopicActionCollection tac WHERE tac.action = ?1", Long.class)
					.setParameter(1, action)
					.getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findAllByActionCount][TopicActionCollectionDao]", e);
			}
		}
		return 0L;
	}

	@Override
	public Stream<ForumActionEnum> findAllAction() {
		return entityManager.createQuery("SELECT DISTINCT tac.action FROM TopicActionCollection tac", ForumActionEnum.class).getResultStream();
	}

	@Override
	public EnumMap<ForumActionEnum, Long> groupMemberAction(long memberId, Collection<ForumActionEnum> allowActions) {
		if(allowActions == null || allowActions.isEmpty()){
			return new EnumMap<>(ForumActionEnum.class);
		}
		@SuppressWarnings("unchecked")
		List<Object[]> result = entityManager.createQuery("SELECT tac.action, COUNT(tac) FROM TopicActionCollection tac WHERE tac.victim = ?1 AND tac.action IN ?2 GROUP BY tac.action")
				.setParameter(1, memberId)
				.setParameter(2, allowActions)
				.getResultList();
		return groupMemberActionResultMapping(result);
	}

	@Override
	public Map<Long, EnumMap<ForumActionEnum, Long>> groupMemberAction(List<Long> memberIdList, Collection<ForumActionEnum> allowActions) {
		if(allowActions == null || allowActions.isEmpty()){
			return Collections.emptyMap();
		}
		Map<Long, EnumMap<ForumActionEnum, Long>> data = new HashMap<>();
		for(Long memberId : memberIdList){
			EnumMap<ForumActionEnum, Long> stats = groupMemberAction(memberId, allowActions);
			if(!stats.isEmpty()){
				data.put(memberId, stats);
			}
		}
		return data;
	}

	private EnumMap<ForumActionEnum, Long> groupMemberActionResultMapping(List<Object[]> result){
		EnumMap<ForumActionEnum, Long> data = new EnumMap<>(ForumActionEnum.class);
		if (result == null || result.isEmpty()) {
			return data;
		}
		for (Object[] objArr : result) {
			ForumActionEnum action = null;
			if (objArr[0] instanceof ForumActionEnum) {
				action = (ForumActionEnum) objArr[0];
			}
			if (action == null) {
				continue;
			}
			Long size = 0L;
			try{
				size = ((BigDecimal)objArr[1]).longValue();
			}catch(ClassCastException e){
				size = (Long) objArr[1];
			}
			data.put(action, size);
		}
		return data;
	}

	@Override
	public EnumMap<ForumActionEnum, Long> groupAction(List<ForumActionEnum> allowActions) {
		if(allowActions == null || allowActions.isEmpty()){
			return new EnumMap<>(ForumActionEnum.class);
		}
		@SuppressWarnings("unchecked")
		List<Object[]> result = entityManager.createQuery("SELECT tac.action, COUNT(tac) FROM TopicActionCollection tac WHERE tac.action IN ?1 GROUP BY tac.action").setParameter(1, allowActions).getResultList();
		return groupMemberActionResultMapping(result);
	}

	@Override
	public Map<Long, Map<String, Long>> groupMemberAction(ForumActionEnum action, int size) {
		if(action == null){
			return Collections.emptyMap();
		}
		@SuppressWarnings("unchecked")
		List<Object[]> result = entityManager.createQuery("SELECT tac.memberId, tac.memberNickname, COUNT(tac) FROM TopicActionCollection tac WHERE tac.action = ?1 GROUP BY tac.memberId ORDER BY COUNT(tac) DESC")
				.setParameter(1, action)
				.setMaxResults(size)
				.getResultList();
		Map<Long, Map<String, Long>> data = new HashMap<>();
		for(Object[] columns : result){
			Map<String, Long> iM = new HashMap<>();
			Long tmpV = 0L;
			try{
				tmpV = ((BigDecimal)columns[2]).longValue();
			}catch(ClassCastException e){
				tmpV = (Long) columns[2];
			}
			Long tmpK = 0L;
			try{
				tmpK = ((BigDecimal)columns[0]).longValue();
			}catch(ClassCastException e){
				tmpK = (Long) columns[0];
			}
			iM.put(columns[1].toString(), tmpV);
			data.put(tmpK, iM);
		}
		return data;
	}

	@Override
	public Page<TopicActionCollection> findAllByActionAndMember(ForumActionEnum action, long memberId, Pageable pageable) {
		final long total = findAllByActionAndMemberCount(action, memberId);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<TopicActionCollection> query = entityManager.createQuery("SELECT tac FROM TopicActionCollection tac WHERE tac.action = ?1 AND tac.memberId = ?2 ORDER BY tac.entryDateTime DESC", TopicActionCollection.class)
				.setParameter(1, action)
				.setParameter(2, memberId);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

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

			@Override
			public Stream<TopicActionCollection> getResult() {
				return result;
			}
		};
	}
	@Override
	public long findAllByActionAndMemberCount(ForumActionEnum action, long memberId){
		try {
			return entityManager
					.createQuery("SELECT COUNT(tac) FROM TopicActionCollection tac WHERE tac.action = ?1 AND tac.memberId = ?2", Long.class)
					.setParameter(1, action)
					.setParameter(2, memberId)
					.getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findAllByActionAndMemberCount][TopicActionCollectionDao]", e);
			}
		}
		return 0L;
	}

	@Override
	public Page<TopicActionCollection> findAll(LocalDateTime start, LocalDateTime finish, Pageable pageable) {
		final long total = findAllCount(start, finish);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<TopicActionCollection> query = entityManager.createQuery("SELECT tac FROM TopicActionCollection tac WHERE tac.entryDateTime BETWEEN ?1 AND ?2 ORDER BY tac.entryDateTime DESC", TopicActionCollection.class)
				.setParameter(1, start)
				.setParameter(2, finish);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

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

			@Override
			public Stream<TopicActionCollection> getResult() {
				return result;
			}
		};
	}
	
	private long findAllCount(LocalDateTime start, LocalDateTime finish){
		try {
			return entityManager.createQuery("SELECT COUNT(tac) FROM TopicActionCollection tac WHERE tac.entryDateTime BETWEEN ?1 AND ?2", Long.class)
					.setParameter(1, start)
					.setParameter(2, finish)
					.getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[findAllCount][TopicActionCollectionDao]", e);
			}
		}
		return 0L;
	}

	@Override
	public Optional<TopicActionCollection> findOneFavoriteRecord(long memberId, long topicId) {
		try{
			TopicActionCollection tac = entityManager.createQuery("SELECT tac FROM TopicActionCollection tac WHERE tac.action = ?1 AND tac.memberId = ?2 AND tac.topicId = ?3", TopicActionCollection.class)
				.setParameter(1, ForumActionEnum.TOPIC_FAVORITE)
				.setParameter(2, memberId)
				.setParameter(3, topicId)
				.getSingleResult();
			return Optional.ofNullable(tac);
		}catch(javax.persistence.NoResultException e){
			if (logger.isDebugEnabled()) {
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.empty();
	}

	@Override
	public Optional<TopicActionCollection> findOneLikeRecord(long memberId, long topicId) {
		try{
			TopicActionCollection tac = entityManager.createQuery("SELECT tac FROM TopicActionCollection tac WHERE tac.action = ?1 AND tac.memberId = ?2 AND tac.topicId = ?3", TopicActionCollection.class)
				.setParameter(1, ForumActionEnum.TOPIC_LIKED)
				.setParameter(2, memberId)
				.setParameter(3, topicId)
				.getSingleResult();
			return Optional.ofNullable(tac);
		}catch(javax.persistence.NoResultException e){
			if (logger.isDebugEnabled()) {
				logger.debug(e.getMessage(), e);
			}
		}
		return Optional.empty();
	}

	@Override
	public Stream<TopicActionCollection> findAllByMemberAction(long memberId, long topicId, ForumActionEnum action) {
		return entityManager.createQuery("SELECT tac FROM TopicActionCollection tac WHERE tac.action = ?1 AND tac.memberId = ?2 AND tac.topicId = ?3", TopicActionCollection.class)
				.setParameter(1, action)
				.setParameter(2, memberId)
				.setParameter(3, topicId)
				.getResultStream();
	}

	@Override
	public long countMemberAction(long memberId, long topicId, ForumActionEnum action) {
		logger.info("[TopicActionDao][count]args: topic id="+topicId+", member id="+memberId+", action="+action.name());
		try {
			return entityManager.createQuery("SELECT COUNT(tac) FROM TopicActionCollection tac WHERE tac.action = ?1 AND tac.memberId = ?2 AND tac.topicId = ?3", Long.class)
					.setParameter(1, action)
					.setParameter(2, memberId)
					.setParameter(3, topicId)
					.getSingleResult();
		} catch (Exception e) {
			logger.info("[TopicActionDao][count]happed exception: "+e.getMessage());
			if (logger.isDebugEnabled()) {
				logger.debug("[countMemberAction][TopicActionCollectionDao]", e);
			}
		}
		return 0L;
	}

	@Override
	public Page<TopicActionCollection> findByTopic(long topicId, Pageable pageable) {
		final long total = countByTopicAction(topicId);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<TopicActionCollection> query = entityManager.createQuery("SELECT tac FROM TopicActionCollection tac WHERE tac.topicId = ?1 ORDER BY tac.entryDateTime DESC", TopicActionCollection.class).setParameter(1, topicId);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

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

			@Override
			public Stream<TopicActionCollection> getResult() {
				return result;
			}
		};
	}
	
	private long countByTopicAction(long topicId){
		try {
			return entityManager.createQuery("SELECT COUNT(tac) FROM TopicActionCollection tac WHERE tac.topicId = ?1", Long.class)
					.setParameter(1, topicId)
					.getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[countByTopicAction][TopicActionCollectionDao]", e);
			}
		}
		return 0L;
	}
	
	@Override
	public Page<TopicActionCollection> findAllByActionAndMember(List<ForumActionEnum> actions, long memberId, Pageable pageable) {
		if(actions== null || actions.isEmpty()){
			return emptyResult();
		}
		final long total = countAllByActionAndMember(actions, memberId);
		if (total == 0) {
			return emptyResult();
		}
		TypedQuery<TopicActionCollection> query = entityManager.createQuery("SELECT tac FROM TopicActionCollection tac WHERE tac.memberId = ?1 AND tac.action IN ?2 ORDER BY tac.entryDateTime DESC", TopicActionCollection.class)
				.setParameter(1, memberId)
				.setParameter(2, actions);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

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

			@Override
			public Stream<TopicActionCollection> getResult() {
				return result;
			}
		};
	}
	
	private long countAllByActionAndMember(List<ForumActionEnum> actions, long memberId){
		if(actions== null || actions.isEmpty()){
			return 0L;
		}
		try {
			return entityManager.createQuery("SELECT COUNT(tac) FROM TopicActionCollection tac WHERE tac.memberId = ?1 AND tac.action IN ?2", Long.class)
					.setParameter(1, memberId)
					.setParameter(2, actions)
					.getSingleResult();
		} catch (Exception e) {
			if (logger.isDebugEnabled()) {
				logger.debug("[countAllByActionAndMember][TopicActionCollectionDao]", e);
			}
		}
		return 0L;
	}

	@Override
	public Optional<Boolean> isLiked(long topicId, long memberId)throws IllegalStateException {
		logger.info("[TopicActionDao][like]args: topic id="+topicId+", member id="+memberId);
		if(memberId > 0 && topicId > 0){
			Long data = countMemberAction(memberId, topicId, ForumActionEnum.TOPIC_LIKED);
			logger.info("[TopicActionDao][like]result: size="+data);
			if(data!=null && data>0){
				logger.info("[TopicActionDao][like]result: is exist");
				throw new IllegalStateException("已经点过赞了");
			}
			return Optional.of(true);
		}
		throw new IllegalStateException("非法的参数");
	}

	@Override
	public Optional<Boolean> isFavorited(long topicId, long memberId)throws IllegalStateException {
		logger.info("[TopicActionDao][favorite]args: topic id="+topicId+", member id="+memberId);
		if(memberId > 0 && topicId > 0){
			Long data = countMemberAction(memberId, topicId, ForumActionEnum.TOPIC_FAVORITE);
			logger.info("[TopicActionDao][favorite]favorite: size="+data);
			if(data!=null && data>0){
				logger.info("[TopicActionDao][favorite]result: is exist");
				throw new IllegalStateException("收藏记录已经存在");
			}
			return Optional.of(true);
		}
		throw new IllegalStateException("非法的参数");
	}

	@Transactional(propagation = Propagation.REQUIRED)
	@Override
	public int batchSave(Collection<TopicActionCollection> entities) {
		if (null == entities || entities.isEmpty()) {
			return 0;
		}
		
		return entities.stream().reduce(0, (index, tac) -> {
			try {
				save(tac);
				return index + 1;
			} catch (Exception e) {
				return index;
			}
		}, Integer::sum);
	}
}
