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

import com.apobates.forum.event.elderly.ForumActionEnum;
import com.apobates.forum.event.elderly.MemberActionDescriptor;
import com.apobates.forum.member.MemberProfile;
import com.apobates.forum.member.MemberProfileBean;
import com.apobates.forum.member.dao.*;
import com.apobates.forum.member.entity.*;
import com.apobates.forum.member.exception.MemberNamesExistException;
import com.apobates.forum.member.exception.MemberNamesProtectException;
import com.apobates.forum.member.impl.MemberAction;
import com.apobates.forum.member.impl.event.MemberSignInEvent;
import com.apobates.forum.member.impl.event.MemberSignOutEvent;
import com.apobates.forum.member.impl.event.MemberSignUpEvent;
import com.apobates.forum.member.impl.event.MemberVipExchangeEvent;
import com.apobates.forum.member.service.MemberLevelService;
import com.apobates.forum.member.service.MemberService;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.DateTimeUtils;
import com.apobates.forum.utils.lang.TriFunction;
import com.github.davidmarquis.redisq.producer.MessageProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 *
 * @author xiaofanku
 * @since 20200510
 */
@Service
@CacheConfig(cacheNames = "memberCache")
public class MemberServiceImpl implements MemberService{
    @Autowired
    private MemberDao memberDao;
    @Autowired
    private ForumScoreRoleDao forumScoreRoleDao;
    @Autowired
    private MemberNamesProtectDao memberNamesProtectDao;
    @Autowired
    private RegisteInviteCodeDao registeInviteCodeDao;
    @Autowired
    private MemberActiveRecordsDao memberActiveRecordsDao;
    @Autowired
    private MemberLevelService memberLevelService;
    @Autowired
    private MemberVipExchangeRecordsDao memberVipExchangeRecordsDao;
    @Autowired @Qualifier("signUpEventProducer")
    private MessageProducer<MemberSignUpEvent> signUpEventProducer;
    @Autowired @Qualifier("signInEventProducer")
    private MessageProducer<MemberSignInEvent> signInEventProducer;
    @Autowired @Qualifier("signOutEventProducer")
    private MessageProducer<MemberSignOutEvent> signOutEventProducer;
    @Autowired @Qualifier("vipExchangeEventProducer")
    private MessageProducer<MemberVipExchangeEvent> vipExchangeEventProducer;
    private final static Logger logger = LoggerFactory.getLogger(MemberServiceImpl.class);

    @Cacheable(key="'avatar_'+#id", unless="#result==null")
    @Override
    public Optional<String> getMemberAvatar(long id) {
        if(id>0){
            return memberDao.getAvatar(id);
        }
        return Optional.empty();
    }
    
    @MemberAction(action = ForumActionEnum.MEMBER_PROFILE_AVATAR)
    @CacheEvict(key="'avatar_'+#id")
    @Override
    public Optional<Boolean> updateAvatar(long id, String encodeAvtarFormatPath, MemberActionDescriptor actionDescriptor) { //ASP-M8
        return memberDao.editAvatar(id, encodeAvtarFormatPath);
    }
    
    @Override
    public Optional<MemberProfileBean> calcMemberProfileBean(final long id, final Map<ForumActionEnum, Long> otherActionStats) { 
        /*Optional<Member> member = get(id);
        if(!member.isPresent()){
            return Optional.of(MemberProfileBean.guest());
        }
        //定义的积分规则(true=会员的积分规则)
        Map<Boolean, List<ForumScoreRole>> scoreRoleDefineSet = forumScoreRoleDao.findAllUsed().filter(Objects::nonNull).collect(Collectors.partitioningBy(fsr -> fsr.getAction().getSection().equals("member")));
        //积分规则定义中的非会员部分
        Map<ForumActionEnum, Long> otherActiones = scoreRoleDefineSet.getOrDefault(false, new ArrayList<>()).stream().filter(Objects::nonNull).collect(Collectors.toMap(ForumScoreRole::getAction, fsr->Commons.optional(otherActionStats.getOrDefault(fsr.getAction(), 0L), 0L)));
        //积分规则定义中的会员部分
        Map<ForumActionEnum, Long> memberActiones = memberActiveRecordsDao.statsMemberAction(id, scoreRoleDefineSet.getOrDefault(true, new ArrayList<>()).stream().filter(Objects::nonNull).map(ForumScoreRole::getAction).collect(Collectors.toList()));
        otherActiones.putAll(memberActiones);
        //
        List<ForumScoreRole> result = scoreRoleDefineSet.values().stream().flatMap(x -> x.stream()).collect(Collectors.toList());
        Member m = member.get();
        Optional<MemberProfile> one = MemberProfile.init(
                m.getId(),
                m.getNickname(),
                m.getMgroup(),
                m.getMrole(),
                m.getSignature()).calcMemberScore(m.getId(), otherActiones, result);
        if (one.isPresent()) {
            one = one.get().calcMemberLevel(memberLevelService.getMemberLevelCommonBean());
        }
        return one.map(MemberProfile::toResultBean).orElseGet(() -> Optional.of(MemberProfileBean.guest()));*/
        return calcMemberProfileBeanAsync(id, otherActionStats);
    }
    
    @MemberAction(action = ForumActionEnum.MEMBER_PASSPORT)
    @Override
    public Optional<Boolean> editPswd(long id, String oldUnEncryptPswd, String newUnEncryptPswd, MemberActionDescriptor actionDescriptor) { //ASP-M1
        Optional<String> encryptPswd = memberDao.findSalt(id).map(psalt -> Commons.sha256(oldUnEncryptPswd, psalt));
        if(!encryptPswd.isPresent()){
            return Optional.empty();
        }
        //
        String newSalt = Commons.randomAlphaNumeric(6);
        String newEncryptPswd = Commons.sha256(newUnEncryptPswd, newSalt);
        return memberDao.editPswd(id, encryptPswd.get(), newEncryptPswd, newSalt) == 1 ? Optional.of(true) : Optional.empty();
    }
    
    @MemberAction(action = ForumActionEnum.MEMBER_PASSPORT_REST)
    @Override
    public Optional<Boolean> resetPswd(long id, String newUnEncryptPswd, MemberActionDescriptor actionDescriptor) { //ASP-M2
        String newSalt = Commons.randomAlphaNumeric(6);
        String newEncryptPswd = Commons.sha256(newUnEncryptPswd, newSalt);
        return memberDao.resetPswd(id, newEncryptPswd, newSalt) == 1 ? Optional.of(true) : Optional.empty();
    }
    
    @MemberAction(action = ForumActionEnum.MEMBER_PROFILE_BASE)
    @Override
    public Optional<Boolean> edit(long id, String signature, String nickname, MemberActionDescriptor actionDescriptor) { //ASP-M9
        return memberDao.editNicknameAndSignature(id, nickname, signature);
    }
    
    @Override
    public Optional<Boolean> checkNamesUnique(String memberNames)throws MemberNamesProtectException,MemberNamesExistException {
        if (checkNamesProtect(memberNames)) {
            throw new MemberNamesProtectException("帐号暂时不允许使用");
        }
        long mid = memberDao.exists(memberNames);
        if (mid > 0) {
            throw new MemberNamesExistException("用户名已经存在");
        }
        return Optional.of(true);
    }
    
    @MemberAction(action = ForumActionEnum.MEMBER_REGISTER, keyName="memberNames", keyType=String.class)
    @Override
    public Optional<Member> signUp(String memberNames, String unEncryptPswd, String inviteCode, String nickname, MemberActionDescriptor actionDescriptor)throws IllegalArgumentException,IllegalStateException { //ASP-M3
        //--------------------------------------------------------------邀请码检查
        long inviteCodeId = verifyInviteCode(inviteCode);
        //--------------------------------------------------------------登录帐号的唯一性|名称保护检查
        checkNamesUnique(memberNames).orElseThrow(()->new IllegalArgumentException("会员唯一性检查失败"));
        //--------------------------------------------------------------
        String salt = Commons.randomAlphaNumeric(6);
        String encryptPswd = Commons.sha256(unEncryptPswd, salt);
        Member m = new Member(0, memberNames, encryptPswd, salt, nickname);
        m.setInviteCode(inviteCode);
        m.setInviteCodeId(inviteCodeId);
        try {
            memberDao.save(m);
            if (m.getId() > 0) {
                //清理属性,保证对外安全
                Optional<Member> tmp = securityMember(m);
                if (tmp.isPresent()) {
                    Member eventMember = tmp.get();
                    //丢掉了inviteCode相关信息
                    eventMember.setInviteCodeId(inviteCodeId);
                    eventMember.setInviteCode(inviteCode);
                    signUpEventProducer.create(new MemberSignUpEvent(eventMember)).submit();
                }
                //
                return tmp;
            }
        } catch (Exception e) {
            if(logger.isDebugEnabled()){
                logger.debug("注册会员操作失败", e);
            }
        }
        throw new IllegalStateException("注册会员操作失败");
    }
    
    @MemberAction(action = ForumActionEnum.MEMBER_REGISTER, keyName="memberNames", keyType=String.class)
    @Override
    public long create(String memberNames, String unEncryptPswd, String nickname, MemberRoleEnum role, MemberActionDescriptor actionDescriptor)throws IllegalArgumentException,IllegalStateException { //ASP-M4
        //--------------------------------------------------------------检查会员登录帐号的唯一性
        checkNamesUnique(memberNames).orElseThrow(()->new IllegalArgumentException("会员唯一性检查失败"));
        //--------------------------------------------------------------
        String salt = Commons.randomAlphaNumeric(6);
        String encryptPswd = Commons.sha256(unEncryptPswd, salt);
        Member m = new Member(0, memberNames, encryptPswd, salt, MemberStatusEnum.ACTIVE, MemberGroupEnum.LEADER, role, nickname);
        try {
            memberDao.save(m);
            if (m.getId() > 0) {
                return m.getId();
            }
        } catch (Exception e) {
            if(logger.isDebugEnabled()){
                logger.debug("创建社区经理操作失败", e);
            }
        }
        throw new IllegalStateException("注册会员操作失败");
    }
    
    @MemberAction(action = ForumActionEnum.MEMBER_LOGIN, keyName="memberNames", keyType=String.class)
    @Override
    public Optional<Member> signIn(String memberNames, String unEncryptPswd, MemberActionDescriptor actionDescriptor)throws IllegalStateException { //ASP-M5
        String encryptPswd = getMemberSaltPswd(memberNames, unEncryptPswd).orElseThrow(()->new IllegalStateException("会员不存在"));
        //
        Optional<Member> m = memberDao.findOne(memberNames, encryptPswd);
        //清理属性,保证对外安全
        Optional<Member> tmp = securityMember(m);
        if (tmp.isPresent()) {
            Member member = tmp.get();
            MemberSignInEvent sie = new MemberSignInEvent(member, actionDescriptor.location().getIpAddr(), actionDescriptor.getReferrer(), actionDescriptor.getDevice());
            signInEventProducer.create(sie).submit();
            return tmp;
        }
        throw new IllegalStateException("会员不存在或密码错误");
    }
    
    @MemberAction(action = ForumActionEnum.MEMBER_LOGIN, keyName="memberNames", keyType=String.class)
    @Override
    public Optional<Member> signIn(String memberNames, String unEncryptPswd, boolean isAdmin, MemberActionDescriptor actionDescriptor)throws IllegalStateException { //ASP-M6
        if (!isAdmin) {
            return signIn(memberNames, unEncryptPswd, actionDescriptor);
        }
        String encryptPswd = getMemberSaltPswd(memberNames, unEncryptPswd).orElseThrow(()->new IllegalStateException("会员不存在"));
        Optional<Member> data = memberDao.findOneForAdmin(memberNames, encryptPswd);
        //清理属性,保证对外安全
        return securityMember(data);
    }
    
    @MemberAction(action = ForumActionEnum.MEMBER_LOGOUT, keyName="memberNames", keyType=String.class)
    @Override
    public Optional<Boolean> signOut(String memberNames, Member member, MemberActionDescriptor actionDescriptor) { //ASP-M7
        signOutEventProducer.create(new MemberSignOutEvent(member, actionDescriptor.getReferrer(), actionDescriptor.location().getIpAddr())).submit();
        return Optional.of(true);
    }
    
    @Override
    public long existMemberNames(String memberNames) {
        return memberDao.exists(memberNames);
    }
    
    @Override
    public Stream<Member> searchByNames(String suggestNameWord, int pageSize) {
        return memberDao.searchByNames(suggestNameWord, pageSize);
    }
    
    @Override
    public Stream<Member> searchByNames(String suggestNameWord) {
        return memberDao.searchByNames(suggestNameWord);
    }
    
    @Override
    public Stream<Member> queryCollection(List<Long> idList) {
        if (null == idList || idList.isEmpty()) {
            return Stream.empty();
        }
        return memberDao.findAllByIdList(idList);
    }
    
    @Override
    public Stream<Member> getRecent(int size) {
        return memberDao.findAllOfRecent(size);
    }
    
    @Override
    public Optional<Member> get(long id) {
        if (id > 0) {
            //清理属性,保证对外安全
            return securityMember(memberDao.findOneById(id));
        }
        return Optional.empty();
    }
    
    @Override
    public long count() {
        return memberDao.count();
    }
    
    @Override
    public TreeMap<String, Long> groupMemberForBirthDate(LocalDateTime start, LocalDateTime finish) {
        return memberDao.groupMemberForBirthDate(start, finish);
    }
    
    @Override
    public Map<MemberStatusEnum, Long> groupMemberForStatus() {
        return memberDao.groupMemberForStatus();
    }
    
    @Override
    public Map<MemberRoleEnum, Long> groupMemberForRole() {
        return memberDao.groupMemberForRole();
    }
    
    @Override
    public Map<MemberGroupEnum, Long> groupMemberForGroup() {
        return memberDao.groupMemberForGroup();
    }
    
    @Override
    public Map<String, String> getActiveInviteCode(String inviteCode) {
        long inviteCodeId = 0;
        try{
            inviteCodeId = verifyInviteCode(inviteCode);
        }catch(IllegalArgumentException e){}
        return Map.ofEntries(Map.entry("code", inviteCode), Map.entry("id", String.valueOf(inviteCodeId)));
    }

    @Override
    public Optional<Boolean> exchangeVIP(long memberId, int duration, ForumCalendarUnitEnum unit)throws IllegalStateException, IllegalArgumentException {
        return exchangeVIP(memberId, duration, unit, null);
    }
    @Override
    public Optional<Boolean> exchangeVIP(long memberId, int duration, ForumCalendarUnitEnum unit, String transerial) throws IllegalStateException, IllegalArgumentException {
        if(memberId<=1){
            return Optional.empty();
        }
        Member m = get(memberId).orElseThrow(()->new IllegalArgumentException("会员不存在或暂时无法访问"));
        if(MemberGroupEnum.CARD != m.getMgroup()){
            throw new IllegalArgumentException("不被支持的角色变更");
        }
        if(ForumCalendarUnitEnum.MONTH != unit && ForumCalendarUnitEnum.YEAR != unit ){
            throw new IllegalArgumentException("支持的日期单位: Month or Year");
        }
        int limit = duration<=1?1:duration;
        try{
            MemberVipExchangeRecords ver = new MemberVipExchangeRecords(memberId, m.getNickname(), limit, unit);
            if(Commons.isNotBlank(transerial)){
                ver.setSerial(transerial);
            }
            memberVipExchangeRecordsDao.save(ver);
            if(ver.getId()>0) {
                vipExchangeEventProducer.create(new MemberVipExchangeEvent(ver)).submit();
                return Optional.of(true);
            }
        }catch(Exception e){
            throw new IllegalStateException("提升VIP操作失败");
        }
        return Optional.empty();
    }

    private Optional<Member> securityMember(Member member) {
        if (null == member) {
            return Optional.empty();
        }
        Member tmp = new Member();
        tmp.setAvatarURI(member.getAvatarURI());
        tmp.setId(member.getId());
        tmp.setMgroup(member.getMgroup());
        tmp.setMrole(member.getMrole());
        tmp.setNickname(member.getNickname());
        tmp.setSignature(member.getSignature());
        tmp.setTdparty(member.getTdparty());
        tmp.setRegisteDateTime(member.getRegisteDateTime());
        tmp.setStatus(member.getStatus());
        tmp.setNames(member.getNames());
        return Optional.of(tmp);
    }
    
    private Optional<Member> securityMember(Optional<Member> member) {
        Member m = member.orElse(null); //optional:class java.util.LinkedHashMap cannot be cast to class
        return securityMember(m);
    }
    //是禁止返回true
    private boolean checkNamesProtect(String memberNames) {
        Stream<MemberNamesProtect> nativeMnp = MemberNamesProtect.getNativeProtect().stream().map(MemberNamesProtect::new);
        Stream<MemberNamesProtect> definedMnp = memberNamesProtectDao.findAll(true);
        return Stream.concat(nativeMnp, definedMnp).filter(x -> memberNames.toLowerCase().equals(x.getMemberNames().toLowerCase())).findAny().isPresent();
    }
    private Optional<String> getMemberSaltPswd(String memberNames, String unEncryptPswd) {
        return memberDao.findSalt(memberNames).map(psalt -> Commons.sha256(unEncryptPswd, psalt));
    }
    private Optional<MemberProfileBean> calcMemberProfileBeanAsync(final long id, final Map<ForumActionEnum, Long> otherActionStats) {
        final Optional<Member> member = get(id);
        if(!member.isPresent()){
            return Optional.of(MemberProfileBean.guest());
        }
        final TriFunction<Map<ForumActionEnum, Long>, Stream<ForumScoreRole>, Optional<Member>, Optional<MemberProfile>> action = (stats, scoreRoles, om)->{
            Member m = om.get();
            return MemberProfile.init(
                        m.getId(),
                        m.getNickname(),
                        m.getMgroup(),
                        m.getMrole(),
                        m.getSignature())
                    .calcMemberScore(m.getId(), stats, scoreRoles.collect(Collectors.toList()));
        };
        CompletableFuture<Optional<MemberProfile>> mid = getMemberCalcActionAsync(id, otherActionStats).thenCombine(getScoleRolesAsync(), (mapStruct, forumScoreRole)->action.apply(mapStruct, forumScoreRole, member));
        return mid.thenCombine(CompletableFuture.supplyAsync(()->memberLevelService.getMemberLevelCommonBean()), (omp, mlbeans)->{
            omp.ifPresent(mp->mp.calcMemberLevel(mlbeans));
            return omp;
        }).orTimeout(1, TimeUnit.SECONDS).join().map(MemberProfile::toResultBean).orElseGet(() -> Optional.of(MemberProfileBean.guest()));
    }
    private CompletableFuture<Map<ForumActionEnum, Long>> getMemberCalcActionAsync(final long id, final Map<ForumActionEnum, Long> otherActionStats){
        CompletableFuture<Map<Boolean, List<ForumScoreRole>>> scoreRoleDefineSet = getScoleRolesAsync().thenApply(fsrs->fsrs.collect(Collectors.partitioningBy(fsr -> fsr.getAction().getSection().equals("member"))));
        return scoreRoleDefineSet.thenCompose(mp->CompletableFuture.supplyAsync(()->mp.getOrDefault(false, new ArrayList<>()).stream().collect(Collectors.toMap(ForumScoreRole::getAction, fsr->Commons.optional(otherActionStats.getOrDefault(fsr.getAction(), 0L), 0L))))
                                        .thenCombine(CompletableFuture.supplyAsync(()->memberActiveRecordsDao.statsMemberAction(id, mp.getOrDefault(true, new ArrayList<>()).stream().map(ForumScoreRole::getAction).collect(Collectors.toList())))
                                                        .completeOnTimeout(Collections.emptyMap(), 1, TimeUnit.SECONDS), 
                                                (otherActiones, memberActiones)->{
                                                    HashMap<ForumActionEnum, Long> data = new HashMap<>();
                                                    data.putAll(otherActiones);
                                                    data.putAll(memberActiones);
                                                    return data;
                                                }));
    }
    private CompletableFuture<Stream<ForumScoreRole>> getScoleRolesAsync(){
        return CompletableFuture.supplyAsync(()->forumScoreRoleDao.findAllUsed().filter(Objects::nonNull)).completeOnTimeout(Stream.empty(), 1, TimeUnit.SECONDS);
    }
    
    private long verifyInviteCode(String inviteCode)throws IllegalArgumentException{
        long inviteCodeId = 0L;
        if (Commons.isNotBlank(inviteCode)) {
            RegisteInviteCode ic = registeInviteCodeDao.findOne(inviteCode).orElseThrow(()->new IllegalArgumentException("邀请码不存在或已经失效"));
            //邀请码只能是当天有效的
            if(!DateTimeUtils.isSameDay(ic.getBuildDateTime(), LocalDateTime.now())){
                throw new IllegalArgumentException("当前邀请码已经过期");
            }
            inviteCodeId = ic.getId();
        }
        return inviteCodeId;
    }
}