/*
 * Copyright 1999-2022 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.nacos.plugin.auth.impl.jwt;

import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import com.alibaba.nacos.plugin.auth.impl.utils.Base64Decode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.concurrent.TimeUnit;

/**
 * JwtParse.
 *
 * @author Weizhan▪Yun
 * @date 2023/1/15 21:38
 */
@SuppressWarnings("PMD.UndefineMagicConstantRule")
public class NacosJwtParser {

    private static final Logger LOG = LoggerFactory.getLogger(NacosJwtParser.class);
    
    private final NacosSignatureAlgorithm signatureAlgorithm;
    
    private final Key key;
    
    public NacosJwtParser(String base64edKey) {
        this.validKey(base64edKey);
        byte[] decode = Base64Decode.decode(base64edKey);
        int bitLength = decode.length << 3;
        if (bitLength < 256) {
            String msg = "The specified key byte array is " + bitLength + " bits which "
                    + "is not secure enough for any JWT HMAC-SHA algorithm.  The JWT "
                    + "JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a "
                    + "size >= 256 bits (the key size must be greater than or equal to the hash "
                    + "output size).  See https://tools.ietf.org/html/rfc7518#section-3.2 for more information.";
            throw new IllegalArgumentException(msg);
        }
        
        if (bitLength < 384) {
            this.signatureAlgorithm = NacosSignatureAlgorithm.HS256;
        } else if (bitLength < 512) {
            this.signatureAlgorithm = NacosSignatureAlgorithm.HS384;
        } else {
            this.signatureAlgorithm = NacosSignatureAlgorithm.HS512;
        }
        this.key = new SecretKeySpec(decode, signatureAlgorithm.getJcaName());
    }

    private void validKey(String base64edKey) {
        int length = base64edKey.toCharArray().length;
        if (length % 4 != 0) {
            LOG.warn("The secret Key currently in use is not a standard Base64 encoding"
                    + " and will no longer be supported in future versions;");
        }
    }
    
    private String sign(NacosJwtPayload payload) {
        return signatureAlgorithm.sign(payload, key);
    }
    
    public JwtBuilder jwtBuilder() {
        return new JwtBuilder();
    }
    
    public NacosUser parse(String token) throws AccessException {
        return NacosSignatureAlgorithm.verify(token, key);
    }
    
    public long getExpireTimeInSeconds(String token) throws AccessException {
        return NacosSignatureAlgorithm.getExpiredTimeInSeconds(token, key);
    }
    
    public class JwtBuilder {
        
        private final NacosJwtPayload nacosJwtPayload = new NacosJwtPayload();
        
        public JwtBuilder setUserName(String userName) {
            this.nacosJwtPayload.setSub(userName);
            return this;
        }
        
        public JwtBuilder setExpiredTime(long validSeconds) {
            this.nacosJwtPayload.setExp(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) + validSeconds);
            return this;
        }
        
        public String compact() {
            return sign(nacosJwtPayload);
        }
    }
}
