package com.artfess.base.jwt;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import javax.annotation.Resource;

import com.artfess.base.cache.annotation.CacheEvict;
import com.artfess.base.cache.annotation.CachePut;
import com.artfess.base.cache.annotation.Cacheable;
import com.artfess.base.cache.annotation.FirstCache;
import com.artfess.base.cache.annotation.SecondaryCache;
import com.artfess.base.conf.JwtConfig;
import com.artfess.base.constants.CacheKeyConst;
import com.artfess.base.constants.SystemConstants;
import com.artfess.base.context.BaseContext;
import com.artfess.base.util.AppUtil;
import com.artfess.base.util.StringUtil;
import com.artfess.uc.api.model.IUser;
import com.artfess.uc.api.service.IUserService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;



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 {
	@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) {
		return getClaimFromToken(token, Claims::getSubject);
	}

	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 Jwts.parser()
				.setSigningKey(jwtConfig.getSecret())
				.parseClaimsJws(token)
				.getBody();
	}

	public 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 = "";
		String userId = "";
		if(userDetails instanceof IUser ) {
			IUser iUser = (IUser) userDetails;
			tenantId = iUser.getTenantId();
			userId = iUser.getUserId();
		}
		claims.put("tenantId", tenantId);
		claims.put("userId", userId);
		return doGenerateToken(claims, userDetails.getUsername());
	}

	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) {
		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;
	}

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

	/**
	 * <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("userId", currentUserId);
		claims.put("tenantId", tenantId);
		return doGenerateFeignToken(claims,currentUserAccout);
	}


	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.getSecret())
				.compact();
	}

	@SuppressWarnings("unused")
	public Boolean validateFeignToken(String token) {
		final String username = getUsernameFromToken(token);
		BaseContext baseContext = AppUtil.getBean(BaseContext.class);
		final Date created = getIssuedAtDateFromToken(token);
		return (username.equals(baseContext.getCurrentUserAccout())
				&& !isTokenExpired(token));
	}

	/**
	 * <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;
	}

	/**
	 * 删除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) {
	}

	/**
	 * 将token缓存起来
	 * @param userAgent
	 * @param tenantId
	 * @param account
	 * @param expireTime
	 * @param token
	 * @return
	 */
	@CachePut(value = CacheKeyConst.EIP_UC_USER_TOKEN, key = "#userAgent+'_'+#tenantId+'_'+#account+'_'+#ip", 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,String ip, 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+'_'+#ip", ignoreException = false)
	public void removeFromCache(String userAgent, String tenantId, String account,String ip) {
	}

//	public static void main(String[] args) {
//		JwtTokenHandler jwtTokenHandler=new JwtTokenHandler();
//		System.out.println(jwtTokenHandler.getUsernameFromToken("eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsInRlbmFudElkIjoiLTEiLCJleHAiOjE2MjUyMDQxODcsImlhdCI6MTYyNTExNzc4N30.uu5Ixf0wIEwALT72bCmB-mSklCBMRQA7xQTJAxVTYv3w-pFFjopWEQlXZdQAt0SHryh4c93MahXS7wbYLGunUg"));
//	}
}
