
package org.tio.sitexxx.im.common.utils;

import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.core.TioConfig;
import org.tio.http.common.HttpConfig;
import org.tio.http.common.session.HttpSession;
import org.tio.sitexxx.im.common.ImSessionContext;
import org.tio.sitexxx.im.common.bs.HandshakeReq;
import org.tio.sitexxx.service.init.RedisInit;
import org.tio.sitexxx.service.model.main.IpInfo;
import org.tio.sitexxx.service.model.main.User;
import org.tio.sitexxx.service.service.base.IpInfoService;
import org.tio.sitexxx.service.service.base.UserService;
import org.tio.sitexxx.service.utils.AESUtils;
import org.tio.sitexxx.service.vo.*;
import org.tio.utils.cache.caffeineredis.CaffeineRedisCache;
import org.tio.utils.jfinal.P;
import org.tio.utils.lock.SetWithLock;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * @author tanyaowu 
 * 2016年9月13日 上午11:41:13
 */
public class ImUtils {
	private static Logger log = LoggerFactory.getLogger(ImUtils.class);

	/**
	 * 握手时该ChannelContext的User对象<br>
	 * value: User对象<br>
	 */
	private static final String CHANNEL_KEY_HANDSHAKE_USER = "TIO_SITE_HANDSHAKE_USER";

	/**
	 * 握手时该ChannelContext的SimpleUser对象<br>
	 * value: SimpleUser对象<br>
	 */
	private static final String TIO_SITE_HANDSHAKE_SIMPLEUSERVO = "TIO_SITE_HANDSHAKE_SIMPLEUSERVO";

	/**
	 * 设置握手时的User
	 * @param channelContext
	 * @param user
	 */
	public static SimpleUser setHandshakeUser(ChannelContext channelContext, User user) {
		return setHandshakeUser(channelContext, user, null);
	}

	/**
	 * 设置握手时的User
	 * @param channelContext
	 * @param user
	 * @param may
	 * @return
	 */
	public static SimpleUser setHandshakeUser(ChannelContext channelContext, User user, SimpleUser may) {
		channelContext.setAttribute(CHANNEL_KEY_HANDSHAKE_USER, user);
		return setHandshakeSimpleUser(channelContext, user, may);
	}

	/**
	 * 获取握手时的User
	 * @param channelContext
	 */
	public static User getHandshakeUser(ChannelContext channelContext) {
		return (User) channelContext.getAttribute(CHANNEL_KEY_HANDSHAKE_USER);
	}

	/**
	 * 设置握手时的SimpleUser
	 * @param channelContext
	 * @param user
	 * @param may
	 * @return
	 */
	public static SimpleUser setHandshakeSimpleUser(ChannelContext channelContext, User user, SimpleUser may) {
		SimpleUser simpleUser = ImUtils.getSimpleUser(user, channelContext, true);
		simpleUser.setMay(may);
		channelContext.setAttribute(TIO_SITE_HANDSHAKE_SIMPLEUSERVO, simpleUser);
		return simpleUser;
	}

	/**
	 * 获取握手时的SimpleUser
	 * @param channelContext
	 */
	public static SimpleUser getHandshakeSimpleUser(ChannelContext channelContext) {
		return (SimpleUser) channelContext.getAttribute(TIO_SITE_HANDSHAKE_SIMPLEUSERVO);
	}

	/**
	 * 
	 * @author: tanyaowu
	 */
	protected ImUtils() {
	}

	private static final long httpSessionTimeout = P.getLong("http.session.timeout", HttpConfig.DEFAULT_SESSION_TIMEOUT);

	private static final CaffeineRedisCache httpSessionCache = CaffeineRedisCache.register(RedisInit.get(), P.get("http.session.cache.name", HttpConfig.SESSION_CACHE_NAME), null,
	        httpSessionTimeout);

	private static UserService	userService		= UserService.ME;
	public static final String	IMTEST_UID_PRE	= "_imtest_tan_";

	public static User currUser(String token) {
		if (StrUtil.isNotBlank(token)) {
			token = token.replace(" ","+");
			String tokenStr = AESUtils.decrypt(token);
			String[] str = tokenStr.split("###");
			if (str.length < 1) {
				return null;
			}
			int id = Integer.parseInt(str[1]);
			User user = userService.getById(id);
			return userService.checkUserStatus(user) ? user : null;
		}
		return null;
	}

	/**
	 * 根据token获取userid
	 * @param token
	 * @return
	 * @author: tanyaowu
	 */
	public static Integer getUseridByToken(String token) {
		if (StrUtil.isBlank(token)) {
			return null;
		}

		//快速算法 start
		HttpSession session = (HttpSession) httpSessionCache.get(token);
		if (session != null) {
			SessionExt sessionExt = session.getAttribute(SessionKey.SESSION_EXT, SessionExt.class);
			Integer userid = sessionExt.getUid();
			if (userid != null) {
				return userid;
			} else {
				if (StrUtil.startWith(token, IMTEST_UID_PRE)) {
					String _userid = StrUtil.subAfter(token, IMTEST_UID_PRE, false);
					if (StrUtil.isNotBlank(_userid)) {
						userid = Integer.valueOf(_userid);
						return userid;
					}
				}
				log.info("session中并未绑定userid, token:{}", token);
				return null;
			}
		} else {
			log.info("不能根据token[{}]找到session对象", token);
			return null;
		}
		//快速算法 end
	}

	/**
	 * 获取token
	 * @param channelContext
	 * @return
	 * @author tanyaowu
	 */
	public static HandshakeReq getToken(ChannelContext channelContext) {
		ImSessionContext imSessionContext = ImUtils.getImSessionContext(channelContext);
		if (imSessionContext == null) {
			return null;
		}

		return imSessionContext.getHandshakeReq();
	}

	/**
	 * 获取token
	 * @param channelContext
	 * @return
	 * @author tanyaowu
	 */
	public static String getTokenByToken(ChannelContext channelContext) {
		HandshakeReq req = getToken(channelContext);
		return req == null ? "" : req.getToken();
	}


	/**
	 * 获取channelContext对应的用户
	 * @param channelContext
	 * @return
	 * @author: tanyaowu
	 */
	public static User getUser(ChannelContext channelContext) {
		if (channelContext.isVirtual) {
			return getHandshakeUser(channelContext);
		}

		HandshakeReq request = getToken(channelContext);
		if (request == null) {
			return null;
		}
		User user = getUser(request.getToken());
		if (user == null) {
			user = currUser(request.getHttpToken());
		}
		//检查token是不是过期了
		if (user == null) {
			if (StrUtil.isNotBlank(channelContext.userid)) {
				Tio.unbindUser(channelContext);
			}
		}

		return user;
	}

	public static Integer getUid(ChannelContext channelContext) {
		User user = getUser(channelContext);
		if (user == null) {
			return null;
		}

		return user.getId();
	}

	/**
	 * 预警：所有状态下的用户，未处理用户状态，调用方法处，进行状态业务处理
	 * @param token
	 * @return
	 * @author tanyaowu
	 */
	public static User getUser(String token) {
		if (StrUtil.isBlank(token)) {
			return null;
		}

		Integer userid = ImUtils.getUseridByToken(token);
		/**
		 * TO-此处未处理用户状态，调用方法处，进行状态业务处理
		 */
		User user = userService.getById(userid);
		if (user == null) {
			return null;
		} else {
			if (Objects.equals(user.getStatus(), User.Status.NORMAL)) {
				return user;
			} else {
				return null;
			}
		}
	}

	/**
	 * 获取SimpleUser，只在登录状态下返回SimpleUser对象，否则返回null
	 * @param channelContext
	 * @return
	 * @author tanyaowu
	 */
	public static SimpleUser getSimpleUser(ChannelContext channelContext) {
		return getSimpleUser(channelContext, false);
	}

	/**
	 * 获取SimpleUser
	 * @param channelContext
	 * @param force 
	 * 			true: 不管登录与否，都返回SimpleUser对象；
	 * 			false：只在登录状态下返回SimpleUser对象
	 * @return
	 */
	public static SimpleUser getSimpleUser(ChannelContext channelContext, boolean force) {
		User user = getUser(channelContext);
		return getSimpleUser(user, channelContext, force);
	}

	/**
	 * 根据ChannelContext获取User对象
	 * @param user
	 * @param channelContext
	 * @param force 是否强制创建User
	 * @return
	 */
	public static SimpleUser getSimpleUser(User user, ChannelContext channelContext, boolean force) {
		if (!UserService.ME.checkUserStatus(user)) {
			if (!force) {
				return null;
			}
		}
		SimpleUser simpleUser = null;
		if (user != null) {
			simpleUser = SimpleUser.fromUser(user);
			completeSimpleUser(simpleUser, channelContext);
		} else {
			simpleUser = createTourist(channelContext);
		}
		return simpleUser;
	}

	/**
	 * 创建一个游客
	 * @param channelContext
	 * @return
	 * @author tanyaowu
	 */
	public static SimpleUser createTourist(ChannelContext channelContext) {
		SimpleUser simpleUser = new SimpleUser();
		simpleUser.setN("游客" + channelContext.getId());
		completeSimpleUser(simpleUser, channelContext);
		return simpleUser;
	}

	/**
	 * 用ChannelContext完善SimpleUser对象，主要用于IM前端显示
	 * @param simpleUser
	 * @param channelContext
	 */
	public static void completeSimpleUser(SimpleUser simpleUser, ChannelContext channelContext) {
		ImSessionContext imSessionContext = ImUtils.getImSessionContext(channelContext);
		if (imSessionContext.isWebsocket()) {
		} else {
			simpleUser.setMobileInfo(ImUtils.getMobileInfo(channelContext));
		}

		String ip = channelContext.getClientNode().getIp();
		IpInfo ipInfo = IpInfoService.ME.save(ip);

		simpleUser.setCid(channelContext.getId());
		simpleUser.setTimeCreated(channelContext.stat.timeCreated);
		simpleUser.setIpInfo(ipInfo);
		simpleUser.setGroupid(imSessionContext.getGroupid());
	}

	/**
	 * 获取ImSessionContext
	 * @param channelContext
	 * @return
	 * @author tanyaowu
	 */
	public static ImSessionContext getImSessionContext(ChannelContext channelContext) {
		return (ImSessionContext) channelContext.getAttribute(Const.IM_SESSION_KEY);
	}

	/**
	 * 获取HandshakeReq
	 * @param channelContext
	 * @return
	 * @author tanyaowu
	 */
	public static HandshakeReq getHandshakeReq(ChannelContext channelContext) {
		ImSessionContext imSessionContext = ImUtils.getImSessionContext(channelContext);
		return imSessionContext.getHandshakeReq();
	}

	/**
	 * 获取设备类型
	 * @param channelContext
	 * @return
	 * @author tanyaowu
	 */
	public static Devicetype getDevicetype(ChannelContext channelContext) {
		ImSessionContext imSessionContext = ImUtils.getImSessionContext(channelContext);

		if (imSessionContext.isWebsocket()) {
			return Devicetype.WEB;
		}

		HandshakeReq handshakeReq = getHandshakeReq(channelContext);
		return Devicetype.from(handshakeReq.getMobileInfo().getDevicetype());
	}

	/**
	 * 手机过来的连接才有，PC端过来的都会返回null
	 * @param channelContext
	 * @return
	 * @author tanyaowu
	 */
	public static MobileInfo getMobileInfo(ChannelContext channelContext) {
		ImSessionContext imSessionContext = ImUtils.getImSessionContext(channelContext);

		if (imSessionContext.isWebsocket()) {
			return null;
		}

		HandshakeReq handshakeReq = getHandshakeReq(channelContext);
		return handshakeReq.getMobileInfo();
	}

	/**
	 * 重新绑定群组，主要用于重新排序
	 * @param channelContext
	 */
	public static void rebindGroups(ChannelContext channelContext) {
		boolean isModifiedUser = false; //是否设置了握手用户信息，即是否调用了：ImUtils.setHandshakeUser(channelContext, user);
		TioConfig tioConfig = channelContext.tioConfig;
		User user = ImUtils.getUser(channelContext);

		//如果之前已经握过手，则先将该ChannelContext从所有群组中解绑Group，这个主要是为了TreeSet排序的正确性
		SetWithLock<String> setWithLock = channelContext.getGroups();
		if (setWithLock != null) {
			try {
				Set<String> groups = setWithLock.getObj();
				if (groups != null && groups.size() > 0) {
					//1、先备份一下已经所属群组
					Set<String> groupsbak = new HashSet<>(groups.size());
					groupsbak.addAll(groups);

					//2、解绑群组
					for (String groupid : groupsbak) {
						try {
							tioConfig.groups.unbind(groupid, channelContext, true, false);
						} catch (Exception e) {
							log.error("", e);
						}
					}

					//3、修改握手User
					ImUtils.setHandshakeUser(channelContext, user);
					isModifiedUser = true;

					//4、重新绑定群组
					for (String groupid : groupsbak) {
						try {
							tioConfig.groups.bind(groupid, channelContext, false);
						} catch (Exception e) {
							log.error("", e);
						}
					}
				}
			} catch (Throwable e) {
				throw e;
			}
		}

		if (!isModifiedUser) {
			ImUtils.setHandshakeUser(channelContext, user);
		}
	}
}
