/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.dubbo.registry.xds.util.bootstrap;

import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.registry.xds.XdsInitializationException;

import io.envoyproxy.envoy.config.core.v3.Node;
import io.grpc.ChannelCredentials;
import io.grpc.internal.JsonParser;
import io.grpc.internal.JsonUtil;

import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class BootstrapperImpl extends Bootstrapper {

    static final String BOOTSTRAP_PATH_SYS_ENV_VAR = "GRPC_XDS_BOOTSTRAP";
    static String bootstrapPathFromEnvVar = System.getenv(BOOTSTRAP_PATH_SYS_ENV_VAR);

    private static final Logger logger = LoggerFactory.getLogger(BootstrapperImpl.class);
    private FileReader reader = LocalFileReader.INSTANCE;

    private static final String SERVER_FEATURE_XDS_V3 = "xds_v3";
    private static final String SERVER_FEATURE_IGNORE_RESOURCE_DELETION = "ignore_resource_deletion";

    public BootstrapInfo bootstrap() throws XdsInitializationException {
        String filePath = bootstrapPathFromEnvVar;
        String fileContent = null;
        if (filePath != null) {
            try {
                fileContent = reader.readFile(filePath);
            } catch (IOException e) {
                throw new XdsInitializationException("Fail to read bootstrap file", e);
            }
        }
        if (fileContent == null) throw new XdsInitializationException("Cannot find bootstrap configuration");

        Map<String, ?> rawBootstrap;
        try {
            rawBootstrap = (Map<String, ?>) JsonParser.parse(fileContent);
        } catch (IOException e) {
            throw new XdsInitializationException("Failed to parse JSON", e);
        }
        return bootstrap(rawBootstrap);
    }

    @Override
    BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationException {
        BootstrapInfo.Builder builder = new BootstrapInfoImpl.Builder();

        List<?> rawServerConfigs = JsonUtil.getList(rawData, "xds_servers");
        if (rawServerConfigs == null) {
            throw new XdsInitializationException("Invalid bootstrap: 'xds_servers' does not exist.");
        }
        List<ServerInfo> servers = parseServerInfos(rawServerConfigs);
        builder.servers(servers);

        Node.Builder nodeBuilder = Node.newBuilder();
        Map<String, ?> rawNode = JsonUtil.getObject(rawData, "node");
        if (rawNode != null) {
            String id = JsonUtil.getString(rawNode, "id");
            if (id != null) {
                nodeBuilder.setId(id);
            }
            String cluster = JsonUtil.getString(rawNode, "cluster");
            if (cluster != null) {
                nodeBuilder.setCluster(cluster);
            }
            Map<String, ?> metadata = JsonUtil.getObject(rawNode, "metadata");
            Map<String, ?> rawLocality = JsonUtil.getObject(rawNode, "locality");
        }
        builder.node(nodeBuilder.build());

        Map<String, ?> certProvidersBlob = JsonUtil.getObject(rawData, "certificate_providers");
        if (certProvidersBlob != null) {
            Map<String, CertificateProviderInfo> certProviders = new HashMap<>(certProvidersBlob.size());
            for (String name : certProvidersBlob.keySet()) {
                Map<String, ?> valueMap = JsonUtil.getObject(certProvidersBlob, name);
                String pluginName = checkForNull(JsonUtil.getString(valueMap, "plugin_name"), "plugin_name");
                Map<String, ?> config = checkForNull(JsonUtil.getObject(valueMap, "config"), "config");
                CertificateProviderInfoImpl certificateProviderInfo =
                        new CertificateProviderInfoImpl(pluginName, config);
                certProviders.put(name, certificateProviderInfo);
            }
            builder.certProviders(certProviders);
        }

        return builder.build();
    }

    private static List<ServerInfo> parseServerInfos(List<?> rawServerConfigs) throws XdsInitializationException {
        List<ServerInfo> servers = new LinkedList<>();
        List<Map<String, ?>> serverConfigList = JsonUtil.checkObjectList(rawServerConfigs);
        for (Map<String, ?> serverConfig : serverConfigList) {
            String serverUri = JsonUtil.getString(serverConfig, "server_uri");
            if (serverUri == null) {
                throw new XdsInitializationException("Invalid bootstrap: missing 'server_uri'");
            }
            List<?> rawChannelCredsList = JsonUtil.getList(serverConfig, "channel_creds");
            if (rawChannelCredsList == null || rawChannelCredsList.isEmpty()) {
                throw new XdsInitializationException(
                        "Invalid bootstrap: server " + serverUri + " 'channel_creds' required");
            }
            ChannelCredentials channelCredentials =
                    parseChannelCredentials(JsonUtil.checkObjectList(rawChannelCredsList), serverUri);
            //            if (channelCredentials == null) {
            //                throw new XdsInitializationException(
            //                    "Server " + serverUri + ": no supported channel credentials found");
            //            }

            boolean useProtocolV3 = false;
            boolean ignoreResourceDeletion = false;
            List<String> serverFeatures = JsonUtil.getListOfStrings(serverConfig, "server_features");
            if (serverFeatures != null) {
                useProtocolV3 = serverFeatures.contains(SERVER_FEATURE_XDS_V3);
                ignoreResourceDeletion = serverFeatures.contains(SERVER_FEATURE_IGNORE_RESOURCE_DELETION);
            }
            servers.add(new ServerInfoImpl(serverUri, channelCredentials, useProtocolV3, ignoreResourceDeletion));
        }
        return servers;
    }

    void setFileReader(FileReader reader) {
        this.reader = reader;
    }

    /**
     * Reads the content of the file with the given path in the file system.
     */
    interface FileReader {
        String readFile(String path) throws IOException;
    }

    private enum LocalFileReader implements FileReader {
        INSTANCE;

        @Override
        public String readFile(String path) throws IOException {
            return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
        }
    }

    private static <T> T checkForNull(T value, String fieldName) throws XdsInitializationException {
        if (value == null) {
            throw new XdsInitializationException("Invalid bootstrap: '" + fieldName + "' does not exist.");
        }
        return value;
    }

    @Nullable
    private static ChannelCredentials parseChannelCredentials(List<Map<String, ?>> jsonList, String serverUri)
            throws XdsInitializationException {
        return null;
    }
}
