package com.softwarebr.nunu.service.user.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.brframework.common.utils.DateTimeUtils;
import com.brframework.commonapppush.service.PushClientInfoService;
import com.brframework.commondb.core.AbstractEntityService;
import com.brframework.commondb.core.CommonRepository;
import com.brframework.commonsecurity.core.SecuritySubject;
import com.brframework.commonsecurity.core.SecurityUserDetailsService;
import com.brframework.commonweb.exception.HandleException;
import com.brframework.webapppatch.domain.RequestFlag;
import com.google.common.collect.Lists;
import com.softwarebr.nunu.config.domain.ConfigService;
import com.softwarebr.nunu.dao.user.MemberDao;
import com.softwarebr.nunu.domain.PayConfig;
import com.softwarebr.nunu.domain.VisualizationDate;
import com.softwarebr.nunu.dto.user.MemberParamDTO;
import com.softwarebr.nunu.entity.user.Member;
import com.softwarebr.nunu.json.admin.visual.MemberRegisterResult;
import com.softwarebr.nunu.json.admin.visual.VisitorsResult;
import com.softwarebr.nunu.json.api.user.ThirdPartyLoginResult;
import com.softwarebr.nunu.service.hall.InvestOrgService;
import com.softwarebr.nunu.service.user.MemberService;
import com.softwarebr.nunu.util.VisualizationUtil;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @Author lilin
 * @Date 2020-02-17 14:07:07
 */

@Slf4j
@Service
public class MemberServiceImpl extends AbstractEntityService<Member, Long, Object> implements MemberService {

	@Autowired
	private MemberDao memberDao;

	@Autowired
	@Qualifier("apiUserDetailsService")
	SecurityUserDetailsService userDetailsService;

	@Autowired
	private WxMpService wxMpService;

	@Autowired
	private StringRedisTemplate redisTemplate;

	@Autowired
	PushClientInfoService pushClientInfoService;

	@Autowired
	private InvestOrgService investOrgService;

	@Autowired
	ConfigService<PayConfig> payConfigService;

	/** 访问人数收集 */
	private static final String INDEX_ACCESS_STATISTICS_KEY = "INDEX_ACCESS_STATISTICS_KEY";

	@Override
	public CommonRepository<Long, Member> getRepository() {
		return memberDao;
	}

	public MemberService getProxy() {
		if (AopContext.currentProxy() instanceof MemberService) {
			return (MemberService) AopContext.currentProxy();
		} else {
			return this;
		}
	}

	@Override
	public Integer checkRegister(String phone, Integer type) {
		Member member = findByPhone(phone);
		if (null != member && null == type) {
			//手机号码已注册
			return Member.MEMBER_CHECK_REGISTER_REJECT;
		} else if (null != member && null != member.getWxOpenId()) {
			//手机号码已绑定微信
			return Member.MEMBER_CHECK_REGISTER_BINDING;
		} else if(null == member){
			return Member.MEMBER_CHECK_REGISTER_PASS;
		}else{
			return Member.MEMBER_CHECK_REGISTER_BINDING_NO;
		}
	}

	@Override
	public String register(MemberParamDTO paramDTO) {
		Member member = findByPhone(paramDTO.getPhone());
		if (checkRegister(paramDTO.getPhone(), paramDTO.getType()).equals(Member.MEMBER_CHECK_REGISTER_PASS)) {
			member = Member.builder()
					.phone(paramDTO.getPhone())
					.status(Member.STATUS_ENABLE)
					.createDate(LocalDateTime.now())
					.password(new BCryptPasswordEncoder().encode(paramDTO.getPassword()))
					.companyName(paramDTO.getCompanyName())
					.identityType(paramDTO.getIdentityType())
					.username(paramDTO.getUsername())
					.build();
		}
		//第三方账号绑定
		if (null != paramDTO.getType() && null != paramDTO.getThirdPartyId()) {
			member.setWxOpenId(paramDTO.getThirdPartyId());
		}
		member = memberDao.save(member);
		return genJwt(member);
	}

	@Override
	public String login(String username, String password) {
		Member member = findByPhone(username);
		if (member == null) {
			throw new HandleException("该号码未注册，请前往注册!");
		}
		if (member.getStatus().equals(Member.STATUS_PROHIBIT)) {
			throw new HandleException("该用户已被禁用，无法登录!");
		}
		if(! new BCryptPasswordEncoder().matches(password, member.getPassword())){
			throw new HandleException("密码错误");
		}
		return genJwt(member);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void passwordReset(String phone, String password){
		Member member = findByPhone(phone);
		if(member == null){
			throw  new HandleException("用户不存在");
		}
		member.setPassword(new BCryptPasswordEncoder().encode(password));
		save(member);
	}

	@Override
	public ThirdPartyLoginResult thirdPartyLogin(Integer type, String thirdPartyId) throws WxErrorException{
		ThirdPartyLoginResult thirdPartyLoginResult = new ThirdPartyLoginResult();
		String unionId = getThirdPartyId(type, thirdPartyId);
		thirdPartyLoginResult.setOpenId(unionId);
		Member member = getProxy().findThirdParty(type, unionId);
		if (null != member) {
			if (member.getStatus().equals(Member.STATUS_PROHIBIT)) {
				throw new HandleException("该用户已被禁用，无法登录!");
			}
			thirdPartyLoginResult.setToken(genJwt(member));
		}
		return thirdPartyLoginResult;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void saveThirdParty(Integer type, String thirdPartyId, String phone) throws WxErrorException {
		String unionId = getThirdPartyId(type, thirdPartyId);
		Member member = getProxy().findThirdParty(type, thirdPartyId);
		if (null != member) {
			throw new HandleException("该账号已绑定!");
		} else {
			member = findByPhone(phone);
			member.setWxOpenId(unionId);
			memberDao.save(member);
		}
	}

	private String getThirdPartyId(Integer type, String code) throws WxErrorException {
		return getProxy().getWeiXinUnionId(type, code);
	}

	@Override
	public String genJwt(Member member) {
		List<String> authorities = Lists.newArrayList();
		authorities.add("ROLE_USER");
		return userDetailsService.genToken(member.getPhone(), SecuritySubject.builder()
				.id(member.getId().toString())
				.username(member.getPhone())
				.authorities(authorities)
				.build());
	}

	@Override
	public Member findByPhone(String phone) {
		return memberDao.findByPhone(phone);
	}

	@Override
	public Long findMemberIdByPhone(String phone) {
		Member member = memberDao.findByPhone(phone);
		if(null == member){
			throw new HandleException("用户不存在！");
		}
		return member.getId();
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void prohibitMember(Long id){
		Member member = accessObject(id);
		member.setStatus(Member.STATUS_PROHIBIT);
		save(member);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void enableMember(Long id){
		Member member = accessObject(id);
		member.setStatus(Member.STATUS_ENABLE);
		save(member);
	}

	@Override
	public String getWeiXinUnionId(Integer type, String code) throws WxErrorException {
		PayConfig payConfig = payConfigService.get();
		WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl();
		if (Member.USER_THIRD_PARTY_H5_WECHAT.equals(type)) {
			configStorage.setAppId(payConfig.getWxAppletAppId());
			configStorage.setSecret(payConfig.getWxAppletAppSecret());
		} else {
			configStorage.setAppId(payConfig.getWxAppId());
			configStorage.setSecret(payConfig.getWxAppSecret());
		}
		wxMpService.setWxMpConfigStorage(configStorage);
		WxMpOAuth2AccessToken oAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
		WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(oAuth2AccessToken, "zh_CN");
		String unionId = wxMpUser.getUnionId();
		redisTemplate.opsForValue().set(wxMpUser.getUnionId(), JSON.toJSONString(wxMpUser), 60, TimeUnit.MINUTES);
		return unionId;
	}

	@Override
	public Member findThirdParty(Integer type, String openId) {
		if (null != type) {
			return memberDao.findByWxOpenId(openId);
		}
		return null;
	}

	@Override
	public Member bindingPushAndfindUser(String memberId, RequestFlag flag) {
		//推送客户端-用户绑定
		log.info("login-request-flag:{}", JSON.toJSONString(flag));
		pushClientInfoService.binding(memberId, flag.getClientId());
		return accessObject(Long.valueOf(memberId));
	}

	@Override
	public String nicknameHandle(Member user) {
		return StringUtils.isNotEmpty(user.getNickname()) ? user.getNickname() : user.getPhone().substring(0, 3) +"****" + user.getPhone().substring(7);
	}

	@Override
	public String nicknameHandle(Long memberId) {
		Optional<Member> optional = findById(memberId);
		if (optional.isPresent()) {
			Member member = optional.get();
			return nicknameHandle(member);
		}
		return "";
	}


	@Override
	public MemberRegisterResult getMemberRegisterTotal(String minDateTime, String maxDateTime, Long days) {
		VisualizationDate visual = VisualizationUtil.visualDateHandle(minDateTime, maxDateTime, days);
		MemberRegisterResult registerResult = new MemberRegisterResult();
		List<MemberRegisterResult> registerDays = Lists.newArrayList();
		Long personalTotals = 0L;
		Long institutionTotals = 0L;
		Long investmentTotals = 0L;
		for (long i = 0; i <= visual.getDays(); i++) {
			MemberRegisterResult memberRegister = new MemberRegisterResult();
			LocalDateTime startDate = visual.getMaxDate().minusDays(i).with(LocalTime.MIN);
			LocalDateTime endDate = visual.getMaxDate().minusDays(i).with(LocalTime.MAX);
			Long personalTotal = memberDao.countByIdentityTypeAndCreateDateBetween(Member.IDENTITY_TYPE_MEMBER, startDate, endDate);
			Long institutionTotal = memberDao.countByIdentityTypeAndCreateDateBetween(Member.IDENTITY_TYPE_ENTERPRISE, startDate, endDate);
			Long investmentTotal = investOrgService.countInvestOrgByVerifyAdopt(startDate, endDate);
			memberRegister.setPersonalTotal(personalTotal);
			memberRegister.setInstitutionTotal(institutionTotal);
			memberRegister.setInvestmentTotal(investmentTotal);
			memberRegister.setVisualDate(visual.getMaxDate().minusDays(i).format(DateTimeUtils.FORMATTER_DEFAULT_YMD));
			registerDays.add(memberRegister);
			personalTotals = personalTotals + personalTotal;
			institutionTotals = institutionTotals + institutionTotal;
			investmentTotals = investmentTotals + investmentTotal;
		}
		registerResult.setRegisterDays(registerDays);
		registerResult.setPersonalTotal(personalTotals);
		registerResult.setInstitutionTotal(institutionTotals);
		registerResult.setInvestmentTotal(investmentTotals);
		return registerResult;
	}

	@Override
	public void saveAccessStatistics(){
		String accessTime = LocalDateTime.now().format(DateTimeUtils.FORMATTER_DEFAULT_YMD);
		redisTemplate.opsForHash().increment(INDEX_ACCESS_STATISTICS_KEY, accessTime, 1);
	}

	@Override
	public VisitorsResult getMemberVisitorsTotal(String minDateTime, String maxDateTime, Long days) {
		VisualizationDate visual = VisualizationUtil.visualDateHandle(minDateTime, maxDateTime, days);
		VisitorsResult visitorsResult = new VisitorsResult();
		List<VisitorsResult> visitorsDays = Lists.newArrayList();
		Long visitorsTotal = 0L;
		for (long i = 0; i <= visual.getDays(); i++) {
			VisitorsResult visitors = new VisitorsResult();
			String accessTime = visual.getMaxDate().minusDays(i).format(DateTimeUtils.FORMATTER_DEFAULT_YMD);
			Object obj = redisTemplate.opsForHash().get(INDEX_ACCESS_STATISTICS_KEY, accessTime);
			if (null != obj) {
				Long accessTotal = JSONObject.parseObject(obj.toString(), Long.class);
				visitors.setVisitorsTotal(accessTotal);
			} else {
				visitors.setVisitorsTotal(0L);
			}
			visitors.setVisualDate(accessTime);
			visitorsTotal = visitorsTotal + visitors.getVisitorsTotal();
			visitorsDays.add(visitors);
		}
		String risingTime = LocalDateTime.now().minusDays(1).format(DateTimeUtils.FORMATTER_DEFAULT_YMD);
		Object risingObj = redisTemplate.opsForHash().get(INDEX_ACCESS_STATISTICS_KEY, risingTime);
		Long risingTotal = null != risingObj ? JSONObject.parseObject(risingObj.toString(), Long.class) : 0L;
		visitorsResult.setVisitorsDays(visitorsDays);
		visitorsResult.setVisitorsTotal(visitorsTotal);
		visitorsResult.setRisingTotal(null != risingTotal ? risingTotal : 0);
		return visitorsResult;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void updateMember(Long memberId, String headImgUri, String nickname, String username, String companyName, String password) {
		Member member = accessObject(memberId);
		if (StringUtils.isNotEmpty(headImgUri)) {
			member.setHeadImgUri(headImgUri);
		}
		if (StringUtils.isNotEmpty(nickname)) {
			member.setNickname(nickname);
		}

		if(member.getIdentityType().equals(Member.IDENTITY_TYPE_ENTERPRISE)){
			if(StringUtils.isNotEmpty(username)){
				member.setUsername(username);
			}
			if(StringUtils.isNotEmpty(companyName)){
				member.setCompanyName(companyName);
			}
		}
		if(StringUtils.isNotEmpty(password)){
			member.setPassword(new BCryptPasswordEncoder().encode(password));
		}
		memberDao.save(member);
	}
}