package cn.com.capcloud.core.system.security;

import cn.com.capcloud.core.system.exception.CustomException;
import cn.com.capcloud.core.system.role.Role;
import cn.com.capcloud.core.system.user.User;
import cn.com.capcloud.core.system.user.UserService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joda.time.DateTime;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachePut;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Component
public class JwtTokenProvider {

  private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);

  @Value("${security.jwt.token.secret-key:secret-key-gd}")
  private String secretKey;

  @Value("${security.jwt.token.expire-length:18000000}")
  private long validityInMilliseconds;

  @Value("${security.jwt.token.refresh-url:/api/data/save}")
  private String refreshUrl;

  @Value("${server.servlet.context-path}")
  private  String contextPath;

  @Resource
  private MyUserDetails myUserDetails;
  @Resource
  private UserService userService;
  @Resource
  private JdbcTemplate jdbcTemplate;

  @PostConstruct
  protected void init() {
    secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
  }

  // 生成token签名，登录 每次都更新缓存(#后面是设置redis过期时间 60*60*5小时=18000秒，可根据自己业务需求调整)
  @CachePut( value= "createToken#18000",key = "#username", unless="#result == null")
  public String createToken(String username, List<Role> roles) {
    return createToken(username,roles,validityInMilliseconds,secretKey);
  }

  public String createTokenByMultiDevice(String username, List<Role> roles) {
    String token=createToken(username,roles,validityInMilliseconds,secretKey);
    userService.setToken(token,token); //多设备登录用户使用：token-token
    return token;
  }

  public static String createToken(String username, List<Role> roles,long validityInMilliseconds,String secretKey) {
    Claims claims = Jwts.claims().setSubject(username);
    claims.put("auth", roles.stream().map(s -> new SimpleGrantedAuthority("ROLE_"+s.getRoleId())).filter(Objects::nonNull).collect(Collectors.toList()));
    Date now = new Date();
    Date validity = new Date(now.getTime() + validityInMilliseconds);
    return Jwts.builder()
            .setClaims(claims)
            .setIssuedAt(now)
            .setExpiration(validity)
            .signWith(SignatureAlgorithm.HS256, secretKey)
            .compact();
  }

  public Authentication getAuthentication(String token,String requestUrl) {
    String username=getUsername(token);
    User user=userService.findByUserLoginName(username);
    String cacheToken;
    if(user.getIsMultiDeviceLogin().equals("N")){
      cacheToken=userService.getCacheTokenByUsername(username);//获取Redis缓存的key
    }else{
      cacheToken=userService.getCacheTokenByUsername(token);//获取Redis缓存的key（多设备登录的用户）
    }

    if(token.equals(cacheToken)){
      if(requestUrl.equals(contextPath+refreshUrl)){
        if(user.getIsMultiDeviceLogin().equals("N")){
          userService.setToken(username,token);
        }else{
          userService.setToken(token,token);
        }
        DateTime d = new DateTime();
        String updateTime=d.toString("yyyy-MM-dd HH:mm:ss");
        String sql="update tbl_data_online_user set update_time='"+updateTime+"' where token='"+token+"' and status=0";
        jdbcTemplate.execute(sql);
      }
      UserDetails userDetails = myUserDetails.loadUserByUsername(username);
      return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    }else{
      logger.error("签名已过期，请使用最新签名来访问资源："+username);
      throw new CustomException("4010",HttpStatus.UNAUTHORIZED);//未登录不允许访问资源。注：后面的参数：UNAUTHORIZED用不到。
    }
  }

  public String getUsername(String token) {
    return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
  }

  public String resolveToken(HttpServletRequest req) throws AccessDeniedException, InsufficientAuthenticationException {
    String bearerToken = req.getHeader("Authorization");
    if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
      return bearerToken.substring(7);
    }
    return null;
  }
  public boolean validateToken(String token) {
    try {
      Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
      return true;
    } catch (JwtException | IllegalArgumentException e) {
      throw new CustomException("4010",HttpStatus.UNAUTHORIZED);//带签名请求了，但解析失败
    }
  }

}
