package com.hotent.base.jwt;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import javax.annotation.Resource;

import com.hotent.base.feign.UCFeignService;
import com.hotent.base.util.*;
import com.hotent.base.util.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import com.hotent.base.cache.annotation.CacheEvict;
import com.hotent.base.cache.annotation.CachePut;
import com.hotent.base.cache.annotation.Cacheable;
import com.hotent.base.cache.annotation.FirstCache;
import com.hotent.base.cache.annotation.SecondaryCache;
import com.hotent.base.conf.JwtConfig;
import com.hotent.base.constants.CacheKeyConst;
import com.hotent.base.constants.SystemConstants;
import com.hotent.base.context.BaseContext;
import com.hotent.base.exception.BaseException;
import com.hotent.uc.api.model.IUser;
import com.hotent.uc.api.service.IUserService;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultClock;
@Component
public class JwtTokenHandler implements Serializable {
	// 存储失效的 token 及其过期时间
	private final ConcurrentMap<String, Long> tokenBlacklist = new ConcurrentHashMap<>();
	@Resource
	JwtConfig jwtConfig;
	static final String CLAIM_KEY_USERNAME = "sub";
	static final String CLAIM_KEY_CREATED = "iat";
	private static final long serialVersionUID = -3301605591108950415L;
	private Clock clock = DefaultClock.INSTANCE;
	public String getUsernameFromToken(String token) {
		// 检验算法 以防none漏洞攻击
		return getClaimFromToken(token, Claims::getSubject);
	}
	// 将 token 添加到黑名单
	public void addToBlacklist(String token, long expiration) {
		tokenBlacklist.put(token, System.currentTimeMillis() + expiration * 1000);
	}

	// 检查 token 是否在黑名单中
	public boolean isTokenBlacklisted(String token) {
		Long expiration = tokenBlacklist.get(token);
		if (expiration != null) {
			if (System.currentTimeMillis() < expiration) {
				return true;
			} else {
				// 移除已过期的黑名单 token
				tokenBlacklist.remove(token);
			}
		}
		return false;
	}
	/**
	 * jwt none算法漏洞
	 * @param token
	 */
	private void validTokenAlg(String token) {
		String[] split = token.split("\\.");
		JwtAlg jwtAlg = null;
		try {
			String alg = Base64.getFromBase64(split[0]);
			jwtAlg = JsonUtil.toBean(alg, JwtAlg.class);
		} catch (IOException e) {
			throw new BaseException("jwt不允许篡改");
		}
		if(BeanUtils.isEmpty(jwtAlg) ||  !SignatureAlgorithm.HS512.getValue().equals(jwtAlg.getAlg()) ){
			throw new BaseException("不允许篡改加密算法");
		}
	}

	public Date getIssuedAtDateFromToken(String token) {
		return getClaimFromToken(token, Claims::getIssuedAt);
	}

	public Date getExpirationDateFromToken(String token) {
		return getClaimFromToken(token, Claims::getExpiration);
	}

	public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
		final Claims claims = getAllClaimsFromToken(token);
		return claimsResolver.apply(claims);
	}

	private Claims getAllClaimsFromToken(String token) {
		return getAllClaimsFromToken(token,jwtConfig.getSecret());
	}

	private Claims getAllClaimsFromToken(String token,String customSecret) {
		validTokenAlg(token);
		return Jwts.parser()
				.setSigningKey(customSecret)
				.parseClaimsJws(token)
				.getBody();
	}

	private Boolean isTokenExpired(String token) {
		final Date expiration = getExpirationDateFromToken(token);
		return expiration.before(clock.now());
	}

	private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
		return (lastPasswordReset != null && created.before(lastPasswordReset));
	}

	private Boolean ignoreTokenExpiration(String token) {
		// here you specify tokens, for that the expiration is ignored
		return false;
	}

	public String generateToken(String userAccount) {
		IUserService userService = AppUtil.getBean(IUserService.class);
		IUser userByAccount = userService.getUserByAccount(userAccount);
		Assert.notNull(userByAccount, String.format("根据所传账号【%s】未查询到用户", userAccount));
		return generateToken(userByAccount);
	}

	public String generateToken(UserDetails userDetails) {
		Map<String, Object> claims = new HashMap<>();
		String tenantId = "";
		if(userDetails instanceof IUser ) {
			IUser iUser = (IUser) userDetails;
			tenantId = iUser.getTenantId();
		}
		claims.put("tenantId", tenantId);
		return doGenerateToken(claims, userDetails.getUsername());
	}

	public String generateToken(Map<String, Object> claims, String subject) {
		return doGenerateToken(claims, subject);
	}

	public String refreshToken(Map<String, Object> claims, String subject) {
		return doGenerateToken(claims, subject);
	}
	/**
	 * 用于生成外部接口的访问Token
	 * @param claims 包含了sysCode
	 * @param subject
	 * @return
	 */
	public String doOpenApiToken(Map<String, Object> claims, String subject){
		return this.doGenerateToken(claims,subject);
	}

	/**
	 * 该方法用于解析外部接口携带过来的Token
	 * @param token
	 * @return
	 */
	public Claims parseToken(String token){
		return this.getAllClaimsFromToken(token);
	}

	public Claims parseFeignToken(String token){
		return getAllClaimsFromToken(token,jwtConfig.getFeignSecret());
	}

	private String doGenerateToken(Map<String, Object> claims, String subject) {
		final Date createdDate = clock.now();
		final Date expirationDate = calculateExpirationDate(createdDate);

		return Jwts.builder()
				.setClaims(claims)
				.setSubject(subject)
				.setIssuedAt(createdDate)
				.setExpiration(expirationDate)
				.signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
				.compact();
	}

	public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
		final Date created = getIssuedAtDateFromToken(token);
		return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
				&& (!isTokenExpired(token) || ignoreTokenExpiration(token));
	}

	public String refreshToken(String token) {
		final Date createdDate = clock.now();
		final Date expirationDate = calculateExpirationDate(createdDate);

		final Claims claims = getAllClaimsFromToken(token);
		claims.setIssuedAt(createdDate);
		claims.setExpiration(expirationDate);

		return Jwts.builder()
				.setClaims(claims)
				.signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
				.compact();
	}

	@SuppressWarnings("unused")
	public Boolean validateToken(String token, UserDetails userDetails) {
		// 先检查 token 是否在黑名单中
//		if (isTokenBlacklisted(token)) {
//			return false;
//		}
		final String username = getUsernameFromToken(token);
		final Date created = getIssuedAtDateFromToken(token);
		return (username.equals(userDetails.getUsername())
				&& !isTokenExpired(token));
	}

	private Date calculateExpirationDate(Date createdDate) {
		return new Date(createdDate.getTime() + jwtConfig.getExpiration() * 1000);
	}

	public String getTenantIdFromToken(String authToken) {
		Claims allClaimsFromToken = getAllClaimsFromToken(authToken);
		String tenantId = allClaimsFromToken.get("tenantId", String.class);
		return tenantId;
	}

	/**
	 * <pre>feign token </pre>
	 */
	public String generateFeignToken() {
		BaseContext baseContext = AppUtil.getBean(BaseContext.class);
		Map<String, Object> claims = new HashMap<>();
		String currentUserId = baseContext.getCurrentUserId();
		String currentUserAccout = baseContext.getCurrentUserAccout();
		if ("-1".equals(currentUserAccout) || StringUtil.isEmpty(currentUserAccout)) {
			currentUserAccout = SystemConstants.SYSTEM_ACCOUNT;
		}
		String tenantId = baseContext.getCurrentTenantId();
		if("-1".equals(tenantId)){
			claims.put("userId",currentUserId );
		}
		claims.put("tenantId", tenantId);
		return doGenerateFeignToken(claims,currentUserAccout);
	}


	/**
	 * 不能与登录的token的秘钥一致
	 * @param claims
	 * @param subject
	 * @return
	 */
	private String doGenerateFeignToken(Map<String, Object> claims, String subject) {
		final Date createdDate = clock.now();
		final Date expirationDate = calculateFeignExpirationDate(createdDate);

		return Jwts.builder()
				.setClaims(claims)
				.setSubject(subject)
				.setIssuedAt(createdDate)
				.setExpiration(expirationDate)
				.signWith(SignatureAlgorithm.HS512, jwtConfig.getFeignSecret())
				.compact();
	}

	/**
	 * 不能与登录的token的秘钥一致
	 * @param token
	 * @return
	 */
	@SuppressWarnings("unused")
	public Boolean validateFeignToken(String token) {
		Claims allClaimsFromToken = getAllClaimsFromToken(token, jwtConfig.getFeignSecret());
		String username = allClaimsFromToken.getSubject();
		BaseContext baseContext = AppUtil.getBean(BaseContext.class);
		Date expiration = allClaimsFromToken.getExpiration();
		return (username.equals(baseContext.getCurrentUserAccout())
				&& !expiration.before(clock.now()));
	}

	/**
	 * <pre>
	 * feign 请求的token 设置有效时间默认为1天
	 * </pre>
	 * @param createdDate
	 * @return
	 */
	private Date calculateFeignExpirationDate(Date createdDate) {
		return new Date(createdDate.getTime() + jwtConfig.getExpiration() * 1000);
	}

	/**
	 * 获取缓存中的token
	 * @param userAgent
	 * @param tenantId
	 * @param account
	 * @param expireTime
	 * @return
	 */
	@Cacheable(value = CacheKeyConst.EIP_UC_USER_TOKEN, key = "#userAgent+'_'+#tenantId+'_'+#account", ignoreException = false,
//			firstCache = @FirstCache(expireTime = 1800, expireTimeExp = "#expireTime", timeUnit = TimeUnit.SECONDS),
			secondaryCache = @SecondaryCache(expireTime = 1800, expireTimeExp = "#expireTime", preloadTime = 360, forceRefresh = true, timeUnit = TimeUnit.SECONDS))
	public String getTokenFromCache(String userAgent, String tenantId, String account, int expireTime) {
		return null;
	}


	/**
	 * 将token缓存起来
	 * @param userAgent
	 * @param tenantId
	 * @param account
	 * @param expireTime
	 * @param token
	 * @return
	 */
	@CachePut(value = CacheKeyConst.EIP_UC_USER_TOKEN, key = "#userAgent+'_'+#tenantId+'_'+#account", ignoreException = false,
//			firstCache = @FirstCache(expireTime = 1800, expireTimeExp = "#expireTime", timeUnit = TimeUnit.SECONDS),
			secondaryCache = @SecondaryCache(expireTime = 1800, expireTimeExp = "#expireTime", preloadTime = 360, forceRefresh = true, timeUnit = TimeUnit.SECONDS))
	public String putTokenInCache(String userAgent, String tenantId, String account, int expireTime, String token) {
		return token;
	}

	@Cacheable(value = CacheKeyConst.EIP_UC_USER_TOKEN, key = "'custom_koken_'+#customKey", ignoreException = false,
			firstCache = @FirstCache(expireTime = 1800, expireTimeExp = "#expireTime", timeUnit = TimeUnit.SECONDS),
			secondaryCache = @SecondaryCache(expireTime = 1800, expireTimeExp = "#expireTime", preloadTime = 360, forceRefresh = true, timeUnit = TimeUnit.SECONDS))
	public String getCustomTokenFromCache(String customKey, int expireTime) {
		return null;
	}

	@CachePut(value = CacheKeyConst.EIP_UC_CUSTOM_TOKEN, key = "'custom_koken_'+#customKey", ignoreException = false,
//			firstCache = @FirstCache(expireTime = 1800, expireTimeExp = "#expireTime", timeUnit = TimeUnit.SECONDS),
			secondaryCache = @SecondaryCache(expireTime = 1800, expireTimeExp = "#expireTime", preloadTime = 360, forceRefresh = true, timeUnit = TimeUnit.SECONDS))
	public String putCustomTokenInCache(String customKey, int expireTime, String token) {
		return token;
	}

	/**
	 * 删除token
	 * <p>可实现服务端踢用户下线</p>
	 * @param userAgent
	 * @param tenantId
	 * @param account
	 */
	@CacheEvict(value = CacheKeyConst.EIP_UC_USER_TOKEN, key = "#userAgent+'_'+#tenantId+'_'+#account", ignoreException = false)
	public void removeFromCache(String userAgent, String tenantId, String account) {
	}

	/**
	 * 是否开启了Basic认证
	 * @return
	 */
	public Boolean enableBasic() {
		return jwtConfig.isBasic();
	}

	public Claims getClaimsFromToken(String token) {
		return getAllClaimsFromToken(token);
	}

	@CachePut(value = CacheKeyConst.EIP_UC_COUSTOM, key = "'temporary_token_'+#shortCode", ignoreException = false, secondaryCache = @SecondaryCache(timeUnit = TimeUnit.SECONDS, expireTime = 30))
	public String addShortCache(String shortCode, String token) {
		return token;
	}

	@Cacheable(value = CacheKeyConst.EIP_UC_COUSTOM, key = "'temporary_token_'+#shortCode", ignoreException = false)
	public String getShortCache(String shortCode) {
		return null;
	}

	/**
	 * 删除缓存
	 *
	 * @param shortCode
	 */
	@CacheEvict(value = CacheKeyConst.EIP_UC_COUSTOM, key = "'temporary_token_'+#shortCode", ignoreException = false)
	public void removeShortCache(String shortCode) {
	}

	@CachePut(value = CacheKeyConst.EIP_UC_COUSTOM, key = "'temporary_userInfo_'+#account", ignoreException = false,
			secondaryCache = @SecondaryCache(timeUnit = TimeUnit.SECONDS, expireTime = 30))
	public String addUserDetailCache(String account, String userInfo) {	return userInfo; }

	@Cacheable(value = CacheKeyConst.EIP_UC_COUSTOM, key = "'temporary_userInfo_'+#account", ignoreException = false)
	public String getUserDetailCache(String account) { return null;	}

	/**
	 * 删除缓存
	 *
	 * @param account
	 */
	@CacheEvict(value = CacheKeyConst.EIP_UC_COUSTOM, key = "'temporary_userInfo_'+#account", ignoreException = false)
	public void removeUserDetailCache(String account) {
	}




	@CachePut(value = CacheKeyConst.CAPTCHA_CODE_KEY, key = "'code_'+#uuid", ignoreException = false,
			secondaryCache = @SecondaryCache(timeUnit = TimeUnit.SECONDS, expireTime = CacheKeyConst.CAPTCHA_EXPIRATION))
	public String addCaptchaCache(String uuid, String code) {	return code; }

	@Cacheable(value = CacheKeyConst.CAPTCHA_CODE_KEY, key = "'code_'+#uuid", ignoreException = false)
	public String getCaptchaCache(String uuid) { return null;	}

	@Cacheable(value =CacheKeyConst.CAPTCHA_WX_TOKEN_KEY,key = "#appId",firstCache = @FirstCache(timeUnit = TimeUnit.SECONDS,expireTime=CacheKeyConst.WX_TOKEN_EXPIRATION),
			secondaryCache = @SecondaryCache( timeUnit = TimeUnit.SECONDS,expireTime = CacheKeyConst.WX_TOKEN_EXPIRATION))
	public String addWxToken(String appId, String token) {	return token; }

	@Cacheable(value =CacheKeyConst.CAPTCHA_WX_TOKEN_KEY, key="#appId",firstCache = @FirstCache(timeUnit = TimeUnit.SECONDS,expireTime=CacheKeyConst.WX_TOKEN_EXPIRATION),
			secondaryCache = @SecondaryCache( timeUnit = TimeUnit.SECONDS,expireTime = CacheKeyConst.WX_TOKEN_EXPIRATION))
	public String getWxToken(String appId) { return null;	}
	/**
	 * 删除缓存
	 *
	 * @param uuid
	 */
	@CacheEvict(value = CacheKeyConst.CAPTCHA_CODE_KEY, key = "'code_'+#uuid", ignoreException = false)
	public void removeCaptchaCache(String uuid) {
	}
}


class JwtAlg implements Serializable{
	private String alg;

	public String getAlg() {
		return alg;
	}

	public void setAlg(String alg) {
		this.alg = alg;
	}
}