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

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.apobates.forum.core.api.dao.BoardActionCollectionDao;
import com.apobates.forum.core.api.dao.BoardConfigDao;
import com.apobates.forum.core.api.dao.BoardDao;
import com.apobates.forum.core.api.dao.BoardStatsDao;
import com.apobates.forum.core.api.dao.TopicDao;
import com.apobates.forum.core.api.service.BoardService;
import com.apobates.forum.core.entity.Board;
import com.apobates.forum.core.entity.BoardActionCollection;
import com.apobates.forum.core.entity.BoardConfig;
import com.apobates.forum.core.entity.BoardStats;
import com.apobates.forum.core.entity.ForumEntityStatusEnum;
import com.apobates.forum.core.impl.event.BoardCreateEvent;
import com.apobates.forum.core.impl.event.ForumEventPublisher;
import com.apobates.forum.event.elderly.ActionDescriptor;
import com.apobates.forum.event.elderly.ActionEventCulpritor;
import com.apobates.forum.event.elderly.ForumActionEnum;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.DateTimeUtils;

@Service
@CacheConfig(cacheNames = "boardCache")
public class BoardServiceImpl implements BoardService{
	@Autowired
	private BoardDao boardDao;
	@Autowired
	private BoardConfigDao boardConfigDao;
	@Autowired
	private BoardStatsDao boardStatsDao;
	@Autowired
	private TopicDao topicDao;
	@Autowired
	private BoardActionCollectionDao boardActionCollectionDao;
	@Autowired
	private ForumEventPublisher forumEventPublisher;
	
	@Cacheable(key = "#id", unless="#result==null")
	@Override
	public Optional<Board> get(long id) { //[BC]B1
		if(id>0){
			return boardDao.findOne(id);
		}
		return Optional.empty();
	}
	
	@ActionDescriptor(action=ForumActionEnum.BOARD_LOCK)
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> lock(long id, int boardGroupId, ActionEventCulpritor culpritor) { //[BC]B1-R1
		return boardDao.edit(id, ForumEntityStatusEnum.LOCKED);
	}
	
	@ActionDescriptor(action=ForumActionEnum.BOARD_UNLOCK)
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> releaseLock(long id, int boardGroupId, ActionEventCulpritor culpritor) { //[BC]B1-R2
		return boardDao.edit(id, ForumEntityStatusEnum.ACTIVE);
	}
	
	@ActionDescriptor(action=ForumActionEnum.BOARD_REMOVED)
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> remove(long id, int boardGroupId, ActionEventCulpritor culpritor) { //[BC]B1-R3
		return boardDao.edit(id, ForumEntityStatusEnum.DELETE);
	}
	
	@ActionDescriptor(action=ForumActionEnum.BOARD_EDIT)
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> edit(long id, int boardGroupId, String description, ForumEntityStatusEnum status, ActionEventCulpritor culpritor)throws IllegalStateException { //[BC]B1-R4
		Board b = get(id).orElseThrow(()->new IllegalStateException("版块不存在"));
		b.setDescription(description);
		b.setStatus(status);
		
		return boardDao.edit(b);
	}
	
	@CacheEvict(key="#id")
	@Override
	public Optional<Boolean> edit(long id, Board updateBoard)throws IllegalStateException { //[BC]B1-R5
		Board b = get(id).orElseThrow(()->new IllegalStateException("版块不存在"));
		b.setTitle(updateBoard.getTitle());
		b.setDescription(updateBoard.getDescription());
		b.setStatus(updateBoard.getStatus());
		b.setRanking(updateBoard.getRanking());
		//b.setVolumesId(updateBoard.getVolumesId()); 这不在合并版块吗?
		if(Commons.isNotBlank(updateBoard.getImageAddr())){
			b.setImageAddr(updateBoard.getImageAddr());
		}
		return boardDao.edit(b);
	}
	
	@ActionDescriptor(action=ForumActionEnum.BOARD_CONFIG_EDIT)
	@CacheEvict(key="'board_config_'+#id")
	@Override
	public Optional<Boolean> editBoardConfig(long id, int boardGroupId, long configId, BoardConfig updateConfig, ActionEventCulpritor culpritor) throws IllegalStateException{ //[BC]B2-R1
		BoardConfig bc = boardConfigDao.findOne(configId).orElseThrow(()->new IllegalStateException("版块配置文件不存在"));
		bc.setReadWrite(updateConfig.isReadWrite());
		//写
		bc.setEditMinute(updateConfig.getEditMinute());
		bc.setWriteMinInterrupt(updateConfig.getWriteMinInterrupt());
		bc.setWriteMinScore(updateConfig.getWriteMinScore());
		bc.setWriteLowMemberGroup(updateConfig.getWriteLowMemberGroup());
		bc.setWriteLowMemberRole(updateConfig.getWriteLowMemberRole());
		bc.setWriteLowMemberLevel(updateConfig.getWriteLowMemberLevel());
		//读
		bc.setReadMinScore(updateConfig.getReadMinScore());
		bc.setReadLowMemberGroup(updateConfig.getReadLowMemberGroup());
		bc.setReadLowMemberRole(updateConfig.getReadLowMemberRole());
		bc.setReadLowMemberLevel(updateConfig.getReadLowMemberLevel());
		//不分读写
		bc.setIpFilter(updateConfig.isIpFilter());
		return boardConfigDao.edit(bc);
	}
	//
	@Cacheable(key = "'volumes_'+#boardGroupId")
	@Override
	public List<Board> getAllUsedByVolumesId(int boardGroupId) { //[BC]B3
		return boardDao.findUsed(boardGroupId).sorted(Comparator.comparing(Board::getRanking)).collect(Collectors.toList());
	}
	
	@CacheEvict(key="'volumes_'+#boardGroupId")
	@Override
	public Optional<Board> create(int boardGroupId, String title, String description, String encodeImageAddr, ForumEntityStatusEnum status, int ranking)throws IllegalStateException { //[BC]B3-R1
		Board board = new Board(title, description, encodeImageAddr, boardGroupId, status, ranking);
		try {
			boardDao.save(board);
			if(board.getId()>0) {
				//
				forumEventPublisher.publishBoardEvent(new BoardCreateEvent(this, board));
				
				return Optional.of(board);
			}
		}catch(Exception e) {
			throw new IllegalStateException(e.getMessage());
		}
		throw new IllegalStateException("版块创建失败");
	}
	
	@Override
	public Stream<Board> getAllByVolumesId(int boardGroupId) {
		return boardDao.findAllByBoardGroup(boardGroupId);
	}
	
	@Override
	public Stream<Board> getAllUsed() {
		return boardDao.findUsed();
	}
	
	@Override
	public Board get(long id, int boardGroupId) {
		return boardDao.findOneForIndex(id);
	}
	
	@Override
	public Board getTermRelateSection(String directoryNames) {
		return boardDao.findOneTermAssSection(directoryNames);
	}
	
	@Override
	public Stream<Board> getAll() {
		return boardDao.findAll();
	}

	@Override
	public long buildOrigin(int sectionId, String title, String description, String directoryNames, ForumEntityStatusEnum status) throws IllegalStateException{
		if(!checkTermDirectNameUnique(directoryNames).orElse(false)){
			throw new IllegalStateException("子栏目目录名称唯一性检查失败");
		}
		Board board = new Board();
		board.setTitle(title);
		board.setDescription(description);
		board.setVolumesId(sectionId);
		board.setStatus(status);
		board.setOrigin(true);
		board.setDirectoryNames(directoryNames); //需要保证唯一
		board.setEntryDateTime(LocalDateTime.now());
		board.setRanking(0);
		//
		try {
			boardDao.save(board);
			if(board.getId()>0) {
				board.setConfigure(BoardConfig.originConfig(board.getId()));
				//
				forumEventPublisher.publishBoardEvent(new BoardCreateEvent(this, board));
				
				return board.getId();
			}
		}catch(Exception e) {
			throw new IllegalStateException(e.getMessage());
		}
		throw new IllegalStateException("版块创建失败");
	}

	@Override
	public Board getBoardStats(final long id) {
		Function<Long, Optional<BoardStats>> fun = (boardId)->boardStatsDao.findOneByBoard(boardId);
		Optional<Board> opt = get(id);
		opt.ifPresent(b->{
			Optional<BoardStats> optBS = fun.apply(b.getId());
			optBS.ifPresent(bs->{
				bs.setTodayTopices(topicDao.statsBoardTopicSize(id, DateTimeUtils.getTodayEarlyMorning(), LocalDateTime.now()));
			});
			b.setStats(fun.apply(b.getId()).orElse(null));
		});
		return opt.orElse(null);
	}
	
	@ActionDescriptor(action=ForumActionEnum.BOARD_FAVORITE)
	@Override
	public Optional<Boolean> favorite(long id, ActionEventCulpritor culpritor)throws IllegalStateException {
		boolean isFavorited = isFavorited(id, culpritor.getMemberId());
		if(!isFavorited){
			throw new IllegalStateException("收藏记录已经存在");
		}
		//更新版块统计的fac
		return boardStatsDao.plusFavorites(id, culpritor.getMemberId());
	}
	
	@Override
	public boolean isFavorited(long id, long memberId)throws IllegalStateException {
		return boardActionCollectionDao.isFavorited(id, memberId).orElse(false);
	}

	@ActionDescriptor(action=ForumActionEnum.BOARD_FAVORITE_CANCEL)
	@Override
	public Optional<Boolean> removeFavorite(long id, ActionEventCulpritor culpritor)throws IllegalStateException {
		Long data = boardActionCollectionDao.countMemberAction(culpritor.getMemberId(), id, ForumActionEnum.BOARD_FAVORITE_CANCEL);
		if(data!=null && data >0){
			throw new IllegalStateException("已经取消收藏");
		}
		//更新版块统计的fac
		return boardStatsDao.negateFavorites(id, culpritor.getMemberId());
	}

	@Override
	public List<Long> removeFavorites(Set<Long> idSet, ActionEventCulpritor culpritor) {
		List<Long> data = new ArrayList<>();
		if(null == idSet || idSet.isEmpty()){
			return data;
		}
		for(Long boardId : idSet){
			boolean tmp = removeFavorite(boardId, culpritor).orElse(false);
			if(tmp){
				data.add(boardId);
			}
		}
		return data;
	}

	@Override
	public Board getBoardConfig(final long id) {
		final Function<Long, BoardConfig> fun = (boardId)->boardConfigDao.findOneByBoard(id).orElse(null);
		
		Optional<Board> opt = get(id);
		opt.ifPresent((board)->{
			board.setConfigure(fun.apply(board.getId()));
		});
		return opt.orElse(null);
	}

	@Override
	public Map<Long, String> getAllById(Collection<Long> idList) {
		if(idList==null || idList.isEmpty()){
			return Collections.emptyMap();
		}
		return boardDao.findAllById(idList).collect(Collectors.toMap(Board::getId, Board::getTitle));
	}
	
	@Override
	public Stream<Board> getMemberFavorites(long memberId, int size) {
		if (memberId < 1) {
			return Stream.empty();
		}
		final BiFunction<Board, BoardStats, Board> bi = (b, bs) -> {
			Optional<Board> opt = Optional.ofNullable(b);
			opt.ifPresent(br -> {
				BoardStats tmp = Optional.ofNullable(bs).orElse(new BoardStats(br.getId(), br.getVolumesId()));
				br.setStats(tmp);
			});
			return opt.orElse(null);
		};
		CompletableFuture<Set<Long>> boardIdSet = CompletableFuture.supplyAsync(() -> boardActionCollectionDao.findAllMemberFavoriteBoard(memberId, size)).thenApply((Stream<BoardActionCollection> rs) -> rs.map(BoardActionCollection::getBoardId).collect(Collectors.toSet()));
		CompletableFuture<Map<Long, Board>> boardMapCF = boardIdSet.thenCompose(bIdSet -> CompletableFuture.supplyAsync(() -> boardDao.findAllById(bIdSet))).thenApply((Stream<Board> bs) -> bs.collect(Collectors.toMap(Board::getId, Function.identity())));
		CompletableFuture<Map<Long, BoardStats>> boardStatsMapCF = boardIdSet.thenCompose(bIdSet -> CompletableFuture.supplyAsync(() -> boardStatsDao.findAllByBoardId(bIdSet))).thenApply((Stream<BoardStats> bss) -> bss.collect(Collectors.toMap(BoardStats::getBoardId, Function.identity())));
		return boardMapCF.thenCombine(boardStatsMapCF, (bs, bss) -> {
			return bs.values().stream().map(board -> bi.apply(board, bss.get(board.getId()))).filter(Objects::nonNull);
		}).join();
	}
	/*子栏目(原生的版块)*/
	@Override
	public List<Board> getAllTermByOriginId(int sectionId) {
		return (sectionId>0)?boardDao.findAllTermByOriginId(sectionId):getAllTerm();
	}

	@Override
	public List<Board> getAllTerm() {
		return boardDao.findAllTerm();
	}

	@Override
	public Optional<Board> getTermByDirectoryNames(String directoryNames) {
		return boardDao.findOneTermByDirectoryNames(directoryNames);
	}

	@Override
	public Stream<Board> getAllTermById(Collection<Long> idList) {
		if(null == idList || idList.isEmpty()){
			return Stream.empty();
		}
		return boardDao.findAllTermById(idList);
	}

	@Override
	public Optional<Boolean> checkTermDirectNameUnique(String directoryNames)throws IllegalStateException {
		if(!Commons.isNotBlank(directoryNames)){
			throw new IllegalStateException("子栏目的目录名称不可用");
		}
		Long termId = boardDao.existsTermDirectoryNames(directoryNames);
		if(termId > 0){
			throw new IllegalStateException("子栏目的目录名称已被使用");
		}
		return Optional.of(true);
	}

	@Override
	public Optional<Boolean> checkTermDirectNameUnique(String directoryNames, long termId)throws IllegalStateException{ //编辑子栏目时检查目录是否被使用
		if(termId <= 0){
			return checkTermDirectNameUnique(directoryNames);
		}
		//
		if(!Commons.isNotBlank(directoryNames)){
			throw new IllegalStateException("子栏目的目录名称不可用");
		}
		Long count = boardDao.existsTermDirectoryNames(directoryNames, termId);
		if(count > 0){
			throw new IllegalStateException("子栏目的目录名称已被使用");
		}
		return Optional.of(true);
	}

	@Override
	public Optional<Boolean> editTerm(long id, String title, String description, String directoryNames, ForumEntityStatusEnum status) throws IllegalStateException{
		Board term = getTermById(id).orElseThrow(()->new IllegalStateException("子栏目不存在或暂时不可用"));
		term.setTitle(title);
		term.setDescription(description);
		term.setDirectoryNames(directoryNames);
		term.setStatus(status);
		return boardDao.edit(term);
	}

	@Override
	public Optional<Board> getTermById(long termId) {
		if(termId>0){
			return boardDao.findOneTermById(termId);
		}
		return Optional.empty();
	}
	
}
