package ru.micord.ervu.security.webbpm.jwt.service;

import java.lang.invoke.MethodHandles;
import java.util.Base64;
import java.util.Date;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import ru.micord.ervu.security.exception.UnauthorizedException;
import ru.micord.ervu.security.esia.EsiaAuthInfoStore;
import ru.micord.ervu.security.webbpm.jwt.model.Token;

import ru.cg.webbpm.modules.resources.api.ResourceMetadataUtils;

import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken;

/**
 * @author Flyur Karimov
 */
@Component
public class JwtTokenService {

  private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  @Value("${webbpm.security.token.issuer:#{null}}")
  private final String tokenIssuerName =
          ResourceMetadataUtils.PROJECT_GROUP_ID + "." + ResourceMetadataUtils.PROJECT_ARTIFACT_ID;
  private final SecretKey signingKey;


  @Autowired
  public JwtTokenService(@Value("${webbpm.security.token.secret.key:ZjE5ZjMxNmYtODViZC00ZTQ5LWIxZmYtOGEzYzE3Yjc1MDVk}")
                             String secretKey) {
    byte[] encodedKey = Base64.getDecoder().decode(secretKey);
    this.signingKey = Keys.hmacShaKeyFor(encodedKey);
  }

  public Token createAccessToken(String userAccountId, Long expiresIn, String ervuId, Boolean hasRole) {

    Date expirationDate = new Date(System.currentTimeMillis() + 1000L * expiresIn);
    String value = Jwts.builder()
            .setSubject(userAccountId + ":" + ervuId)
            .setIssuer(tokenIssuerName)
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(expirationDate)
            .claim("hasRole", hasRole)
            .signWith(signingKey)
            .compact();
    return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value, hasRole);
  }

  public boolean isValid(Token token) {
    if (!tokenIssuerName.equals(token.getIssuer())) {
      LOGGER.warn("Unknown token issuer {}", token.getIssuer());
      return false;
    }

    if (token.isExpired()) {
      LOGGER.info("Token {} is expired ", token.getValue());
      return false;
    }
    String[] ids = token.getUserAccountId().split(":");
    return EsiaAuthInfoStore.validateAccessToken(ids[0]);
  }

  public Token getToken(String token) {
    Claims claims = Jwts.parser()
        .setSigningKey(signingKey)
        .parseClaimsJws(token)
        .getBody();

    return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token, claims.get("hasRole", Boolean.class));
  }

  public String getAccessToken(HttpServletRequest request) {
    return EsiaAuthInfoStore.getAccessToken(getUserAccountId(request));
  }

  public String getRefreshToken(HttpServletRequest request) {
    return EsiaAuthInfoStore.getRefreshToken(getUserAccountId(request));
  }

  public String getUserAccountId(HttpServletRequest request) {
    String authToken = extractAuthToken(request);

    if (authToken != null) {
      String[] ids = getToken(authToken).getUserAccountId().split(":");
      return ids[0];
    }
    else {
      throw new UnauthorizedException("Failed to get auth data. User unauthorized.");
    }
  }
}
