package com.bonus.core.config;

import cn.hutool.core.util.ArrayUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

@JsonIgnoreProperties(
   ignoreUnknown = true
)
public class AccessToken {
   private static final Logger log = LoggerFactory.getLogger(AccessToken.class);
   @JsonIgnore
   protected static ObjectMapper objectMapper;
   @JsonIgnore
   protected static SecureProperties secureProperties;
   @JsonIgnore
   protected static StringRedisTemplate redisTemplate;
   @JsonIgnore
   protected static AuthenticationPredicate authenticationPredicate;
   private String id;
   private Long subjectId;
   private String subjectName;
   private Map<String, String> subjectData = Maps.newHashMap();
   private String scope;
   private boolean identified;
   private long createTime;
   private long lastTime;

   public static Optional<AccessToken> recovery(String clientToken) {
      StringRedisTemplate var10000 = redisTemplate;
      String var10001 = secureProperties.getServer().getStoreKey();
      clientToken = (String)var10000.boundValueOps(var10001 + ":" + clientToken).get();
      if (StringUtils.isBlank(clientToken)) {
         return Optional.empty();
      } else {
         try {
            AccessToken existToken = (AccessToken)objectMapper.readValue(clientToken, AccessToken.class);
            return authenticationPredicate.authenticated(existToken) ? Optional.of(existToken) : Optional.empty();
         } catch (Exception var2) {
            log.error("Deserialize exist token error", var2);
            return Optional.empty();
         }
      }
   }

   public static AccessToken create(long subjectId) {
      AccessToken accessToken = create();
      accessToken.setSubjectId(subjectId);
      return accessToken;
   }

   public static AccessToken create(long subjectId, String subjectName) {
      AccessToken accessToken = create(subjectId);
      accessToken.setSubjectName(subjectName);
      return accessToken;
   }

   public static AccessToken create() {
      AccessToken accessToken = new AccessToken();
      accessToken.setId(UUID.randomUUID().toString());
      accessToken.setCreateTime(Instant.now().getEpochSecond());
      accessToken.setLastTime(Instant.now().getEpochSecond());
      return accessToken;
   }

   public AccessToken touch() {
      this.identified = this.isAuthenticated();
      this.lastTime = Instant.now().getEpochSecond();
      return this.store();
   }

   @JsonIgnore
   public boolean isAuthenticated() {
      return this.identified && !this.isExpired();
   }

   @JsonIgnore
   private boolean isExpired() {
      return this.lastTime + secureProperties.getExpireAfter() < Instant.now().getEpochSecond();
   }

   public AccessToken withData(Map<String, String> data) {
      this.subjectData = data;
      return this;
   }

   public AccessToken setData(String name, String value) {
      this.subjectData.put(name, value);
      return this;
   }

   public AccessToken removeData(String... keys) {
      if (ArrayUtil.isEmpty(keys)) {
         return this;
      } else {
         String[] var2 = keys;
         int var3 = keys.length;

         for(int var4 = 0; var4 < var3; ++var4) {
            String key = var2[var4];
            this.subjectData.remove(key);
         }

         return this;
      }
   }

   public AccessToken revokeAuthenticate() {
      this.identified = false;
      return this.store();
   }

   public AccessToken authenticate() {
      if (this.subjectId == null) {
         throw new RuntimeException("required subjectId is not provide");
      } else {
         this.identified = true;
         return this;
      }
   }

   public AccessToken store() {
      try {
         StringRedisTemplate var10000 = redisTemplate;
         DefaultRedisScript var10001 = new DefaultRedisScript("redis.call('SET',KEYS[1],ARGV[1],'EX',ARGV[3]);redis.call('SET',KEYS[2],ARGV[2],'EX',ARGV[3]);");
         String[] var10002 = new String[2];
         String var10005 = secureProperties.getServer().getStoreKey();
         var10002[0] = var10005 + ":" + this.getId();
         var10005 = secureProperties.getServer().getSubjectRefTokenKey();
         var10002[1] = var10005 + ":" + this.getSubjectId() + ":" + this.getId() + ":" + this.getCreateTime();
         var10000.execute(var10001, Lists.newArrayList(var10002), new Object[]{objectMapper.writeValueAsString(this), this.getId(), String.valueOf(secureProperties.getServer().getTtl())});
      } catch (Exception var2) {
         log.error("Token store error", var2);
      }

      return this;
   }

   public AccessToken bind() {
      WebContext.get().setAccessToken(this);
      return this;
   }

   public void write() {
      WebContext.get().getResponse().ifPresent((response) -> {
         response.setHeader(secureProperties.getTokenSymbol(), this.getId());
      });
   }

   public void clear() {
      try {
         StringRedisTemplate var10000 = redisTemplate;
         DefaultRedisScript var10001 = new DefaultRedisScript("redis.call('DEL',KEYS[1],KEYS[2]);");
         String[] var10002 = new String[2];
         String var10005 = secureProperties.getServer().getStoreKey();
         var10002[0] = var10005 + ":" + this.getId();
         var10005 = secureProperties.getServer().getSubjectRefTokenKey();
         var10002[1] = var10005 + ":" + this.getSubjectId() + ":" + this.getId() + ":" + this.getCreateTime();
         var10000.execute(var10001, Lists.newArrayList(var10002), new Object[0]);
      } catch (Exception var2) {
         log.error("Token clear error", var2);
      }

   }

   public String getId() {
      return this.id;
   }

   public Long getSubjectId() {
      return this.subjectId;
   }

   public String getSubjectName() {
      return this.subjectName;
   }

   public Map<String, String> getSubjectData() {
      return this.subjectData;
   }

   public String getScope() {
      return this.scope;
   }

   public boolean isIdentified() {
      return this.identified;
   }

   public long getCreateTime() {
      return this.createTime;
   }

   public long getLastTime() {
      return this.lastTime;
   }

   public void setId(final String id) {
      this.id = id;
   }

   public void setSubjectId(final Long subjectId) {
      this.subjectId = subjectId;
   }

   public void setSubjectName(final String subjectName) {
      this.subjectName = subjectName;
   }

   public void setSubjectData(final Map<String, String> subjectData) {
      this.subjectData = subjectData;
   }

   public void setScope(final String scope) {
      this.scope = scope;
   }

   public void setIdentified(final boolean identified) {
      this.identified = identified;
   }

   public void setCreateTime(final long createTime) {
      this.createTime = createTime;
   }

   public void setLastTime(final long lastTime) {
      this.lastTime = lastTime;
   }
}
