/*
 * Copyright 1999-2023 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.common.paramcheck;

import com.alibaba.nacos.common.utils.NumberUtils;
import com.alibaba.nacos.common.utils.PropertyUtils;
import com.alibaba.nacos.common.utils.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * The type Default param checker.
 *
 * @author zhuoguang
 */
public class DefaultParamChecker extends AbstractParamChecker {
    
    private Pattern namespaceShowNamePattern;
    
    private Pattern namespaceIdPattern;
    
    private Pattern dataIdPattern;
    
    private Pattern serviceNamePattern;
    
    private Pattern groupPattern;
    
    private Pattern clusterPattern;
    
    private Pattern ipPattern;
    
    private Pattern mcpNamePattern;
    
    private Pattern agentNamePattern;
    
    private Pattern skillNamePattern;
    
    private static final String CHECKER_TYPE = "default";
    
    private static final String MAX_METADATA_LENGTH_PROP_NAME = "nacos.naming.service.metadata.length";
    
    private static final String MAX_METADATA_LENGTH_ENV_NAME = "NACOS_NAMING_SERVICE_METADATA_LENGTH";
    
    @Override
    public String getCheckerType() {
        return CHECKER_TYPE;
    }
    
    @Override
    public ParamCheckResponse checkParamInfoList(List<ParamInfo> paramInfos) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (paramInfos == null) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        for (ParamInfo paramInfo : paramInfos) {
            paramCheckResponse = checkParamInfoFormat(paramInfo);
            if (!paramCheckResponse.isSuccess()) {
                return paramCheckResponse;
            }
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    @Override
    public void initParamCheckRule() {
        this.paramCheckRule = new ParamCheckRule();
        initFormatPattern();
        replaceParamCheckRuleByEnv();
    }
    
    private void initFormatPattern() {
        this.namespaceShowNamePattern = Pattern.compile(this.paramCheckRule.namespaceShowNamePatternString);
        this.namespaceIdPattern = Pattern.compile(this.paramCheckRule.namespaceIdPatternString);
        this.dataIdPattern = Pattern.compile(this.paramCheckRule.dataIdPatternString);
        this.serviceNamePattern = Pattern.compile(this.paramCheckRule.serviceNamePatternString);
        this.groupPattern = Pattern.compile(this.paramCheckRule.groupPatternString);
        this.clusterPattern = Pattern.compile(this.paramCheckRule.clusterPatternString);
        this.ipPattern = Pattern.compile(this.paramCheckRule.ipPatternString);
        this.mcpNamePattern = Pattern.compile(this.paramCheckRule.mcpNamePatternString);
        this.agentNamePattern = Pattern.compile(this.paramCheckRule.agentNamePatternString);
        this.skillNamePattern = Pattern.compile(this.paramCheckRule.skillNamePatternString);
    }
    
    /**
     * if environment variables exists, it will be replaced.
     */
    private void replaceParamCheckRuleByEnv() {
        String maxMetadataLength = PropertyUtils.getProperty(MAX_METADATA_LENGTH_PROP_NAME, MAX_METADATA_LENGTH_ENV_NAME);
        if (StringUtils.isNotBlank(maxMetadataLength)) {
            this.paramCheckRule.maxMetadataLength = NumberUtils.toInt(maxMetadataLength);
        }
    }
    
    /**
     * Check param info format.
     *
     * @param paramInfo the param info
     * @return the param check response
     */
    public ParamCheckResponse checkParamInfoFormat(ParamInfo paramInfo) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (paramInfo == null) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        paramCheckResponse = checkNamespaceShowNameFormat(paramInfo.getNamespaceShowName());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkNamespaceIdFormat(paramInfo.getNamespaceId());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkDataIdFormat(paramInfo.getDataId());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkServiceNameFormat(paramInfo.getServiceName());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkGroupFormat(paramInfo.getGroup());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkClusterFormat(paramInfo.getClusters());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkSingleClusterFormat(paramInfo.getCluster());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkIpFormat(paramInfo.getIp());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkPortFormat(paramInfo.getPort());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkMetadataFormat(paramInfo.getMetadata());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkMcpNameFormat(paramInfo.getMcpName());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkAgentNameFormat(paramInfo.getAgentName());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse = checkSkillNameFormat(paramInfo.getSkillName());
        if (!paramCheckResponse.isSuccess()) {
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check namespace show name format.
     *
     * @param namespaceShowName the namespace show name
     * @return the param check response
     */
    public ParamCheckResponse checkNamespaceShowNameFormat(String namespaceShowName) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(namespaceShowName)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        if (namespaceShowName.length() > paramCheckRule.maxNamespaceShowNameLength) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(String.format("Param 'namespaceShowName' is illegal, the param length should not exceed %d.",
                    paramCheckRule.maxNamespaceShowNameLength));
            return paramCheckResponse;
        }
        if (!namespaceShowNamePattern.matcher(namespaceShowName).matches()) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage("Param 'namespaceShowName' is illegal, illegal characters should not appear in the param.");
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check namespace id format.
     *
     * @param namespaceId the namespace id
     * @return the param check response
     */
    public ParamCheckResponse checkNamespaceIdFormat(String namespaceId) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(namespaceId)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        if (namespaceId.length() > paramCheckRule.maxNamespaceIdLength) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(String.format("Param 'namespaceId/tenant' is illegal, the param length should not exceed %d.",
                    paramCheckRule.maxNamespaceIdLength));
            return paramCheckResponse;
        }
        if (!namespaceIdPattern.matcher(namespaceId).matches()) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage("Param 'namespaceId/tenant' is illegal, illegal characters should not appear in the param.");
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check data id format.
     *
     * @param dataId the data id
     * @return the param check response
     */
    public ParamCheckResponse checkDataIdFormat(String dataId) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(dataId)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        if (dataId.length() > paramCheckRule.maxDataIdLength) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(
                    String.format("Param 'dataId' is illegal, the param length should not exceed %d.", paramCheckRule.maxDataIdLength));
            return paramCheckResponse;
        }
        if (!dataIdPattern.matcher(dataId).matches()) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage("Param 'dataId' is illegal, illegal characters should not appear in the param.");
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check service name format.
     *
     * @param serviceName the service name
     * @return the param check response
     */
    public ParamCheckResponse checkServiceNameFormat(String serviceName) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(serviceName)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        if (serviceName.length() > paramCheckRule.maxServiceNameLength) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(
                    String.format("Param 'serviceName' is illegal, the param length should not exceed %d.", paramCheckRule.maxServiceNameLength));
            return paramCheckResponse;
        }
        if (!serviceNamePattern.matcher(serviceName).matches()) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage("Param 'serviceName' is illegal, illegal characters should not appear in the param.");
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check group format.
     *
     * @param group the group
     * @return the param check response
     */
    public ParamCheckResponse checkGroupFormat(String group) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(group)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        if (group.length() > paramCheckRule.maxGroupLength) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(
                    String.format("Param 'group' is illegal, the param length should not exceed %d.", paramCheckRule.maxGroupLength));
            return paramCheckResponse;
        }
        if (!groupPattern.matcher(group).matches()) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage("Param 'group' is illegal, illegal characters should not appear in the param.");
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check cluster format.
     *
     * @param clusterString the cluster string
     * @return the param check response
     */
    public ParamCheckResponse checkClusterFormat(String clusterString) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(clusterString)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        String[] clusters = clusterString.split(",");
        for (String cluster : clusters) {
            paramCheckResponse = checkSingleClusterFormat(cluster);
            if (!paramCheckResponse.isSuccess()) {
                return paramCheckResponse;
            }
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check single cluster format.
     *
     * @param cluster the cluster
     * @return the param check response
     */
    public ParamCheckResponse checkSingleClusterFormat(String cluster) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(cluster)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        
        if (cluster.length() > paramCheckRule.maxClusterLength) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(
                    String.format("Param 'cluster' is illegal, the param length should not exceed %d.", paramCheckRule.maxClusterLength));
            return paramCheckResponse;
        }
        if (!clusterPattern.matcher(cluster).matches()) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage("Param 'cluster' is illegal, illegal characters should not appear in the param.");
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check ip format.
     *
     * @param ip the ip
     * @return the param check response
     */
    public ParamCheckResponse checkIpFormat(String ip) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(ip)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        if (ip.length() > paramCheckRule.maxIpLength) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(String.format("Param 'ip' is illegal, the param length should not exceed %d.", paramCheckRule.maxIpLength));
            return paramCheckResponse;
        }
        if (!ipPattern.matcher(ip).matches()) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage("Param 'ip' is illegal, illegal characters should not appear in the param.");
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check port format.
     *
     * @param port the port
     * @return the param check response
     */
    public ParamCheckResponse checkPortFormat(String port) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(port)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        int portInt = 0;
        try {
            portInt = Integer.parseInt(port);
        } catch (Exception e) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(
                    String.format("Param 'port' is illegal, the value should be between %d and %d.", paramCheckRule.minPort, paramCheckRule.maxPort));
            return paramCheckResponse;
        }
        if (portInt > paramCheckRule.maxPort || portInt < paramCheckRule.minPort) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(
                    String.format("Param 'port' is illegal, the value should be between %d and %d.", paramCheckRule.minPort, paramCheckRule.maxPort));
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check metadata format.
     *
     * @param metadata the metadata
     * @return the param check response
     */
    public ParamCheckResponse checkMetadataFormat(Map<String, String> metadata) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (metadata == null || metadata.isEmpty()) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        int totalLength = 0;
        for (Map.Entry<String, String> entry : metadata.entrySet()) {
            if (StringUtils.isNotBlank(entry.getKey())) {
                totalLength = totalLength + entry.getKey().length();
            }
            if (StringUtils.isNotBlank(entry.getValue())) {
                totalLength = totalLength + entry.getValue().length();
            }
        }
        if (totalLength > paramCheckRule.maxMetadataLength) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(
                    String.format("Param 'Metadata' is illegal, the param length should not exceed %d.", paramCheckRule.maxMetadataLength));
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check mcp name format.
     *
     * @param mcpName the mcp name
     * @return the param check response
     */
    public ParamCheckResponse checkMcpNameFormat(String mcpName) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(mcpName)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        if (mcpName.length() > paramCheckRule.maxMcpNameLength) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(
                    String.format("Param 'mcpName' is illegal, the param length should not exceed %d.", paramCheckRule.maxClusterLength));
            return paramCheckResponse;
        }
        if (!mcpNamePattern.matcher(mcpName).matches()) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage("Param 'mcpName' is illegal, illegal characters should not appear in the param.");
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check agent name format.
     *
     * @param agentName agent name
     * @return the param check response
     */
    public ParamCheckResponse checkAgentNameFormat(String agentName) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(agentName)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        if (agentName.length() > paramCheckRule.maxAgentNameLength) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(
                    String.format("Param 'agentName' is illegal, the param length should not exceed %d.", paramCheckRule.maxAgentNameLength));
            return paramCheckResponse;
        }
        if (!agentNamePattern.matcher(agentName).matches()) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage("Param 'agentName' is illegal, illegal characters should not appear in the param.");
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
    
    /**
     * Check skill name format.
     *
     * @param skillName skill name
     * @return the param check response
     */
    public ParamCheckResponse checkSkillNameFormat(String skillName) {
        ParamCheckResponse paramCheckResponse = new ParamCheckResponse();
        if (StringUtils.isBlank(skillName)) {
            paramCheckResponse.setSuccess(true);
            return paramCheckResponse;
        }
        if (skillName.length() > paramCheckRule.maxSkillNameLength) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage("Skill name must be 1-64 characters");
            return paramCheckResponse;
        }
        if (!skillNamePattern.matcher(skillName).matches()) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage(
                    "Skill name may only contain lowercase letters, numbers, and hyphens, and must not start or end with a hyphen");
            return paramCheckResponse;
        }
        if (skillName.contains("--")) {
            paramCheckResponse.setSuccess(false);
            paramCheckResponse.setMessage("Skill name must not contain consecutive hyphens (--)");
            return paramCheckResponse;
        }
        paramCheckResponse.setSuccess(true);
        return paramCheckResponse;
    }
}
