package jp.gr.java_conf.ogibayashi.prometheus;

import java.io.FileNotFoundException;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.orbitz.consul.Consul;
import com.orbitz.consul.HealthClient;
import com.orbitz.consul.model.health.ServiceHealth;

public class PropertyConfig {
    private static final Logger LOG = LoggerFactory.getLogger(PropertyConfig.class);

    public enum Constants {
        CONSUL_SERVER_URL("consul.server.url"),
        CONSUL_KAFKA_SERVICENAME("consul.kafka.servicename"),
        BOOTSTRAP_SERVERS("bootstrap.servers"),
        EXPORTER_PORT("exporter.port"),
        EXPORTER_METRIC_EXPIRE("exporter.metric.expire.seconds"),
        KAFKA_CONSUMER_TOPICS("kafka.consumer.topics"),
        KAKFA_CONSUMER_REMOVEPREFIX("kafka.consumer.remove.prefix");
        
        public final String key;

        Constants(String key) {
            this.key = key;
        }
    }

    private final Properties props;

    public PropertyConfig() {
        props = new Properties();
    }
    
    public PropertyConfig(String propFilePath) throws IOException {
        props = loadProperties(propFilePath);
        props.put("enable.auto.commit", "false");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        changeBootstrapServersIfConsulServerAvailable(props);
    }

    void changeBootstrapServersIfConsulServerAvailable(Properties originalProps) {
        String consulServerUrl = originalProps.getProperty(Constants.CONSUL_SERVER_URL.key);
        String consulKafkaServicename = originalProps.getProperty(Constants.CONSUL_KAFKA_SERVICENAME.key);

        if (consulServerUrl != null && !consulServerUrl.equals("") && consulKafkaServicename != null && !consulKafkaServicename.equals("")) {
            LOG.info("CONSUL: Required properties for using Consul service discovery have been detected");
            ArrayList<String> bootstrapServersArray = getBootStrapServersFromConsul(consulServerUrl, consulKafkaServicename);
            if (bootstrapServersArray != null) {
                String bootstrapServers = String.join(",", bootstrapServersArray);
                if (bootstrapServers != null && !bootstrapServers.equals("")) {
                    String originalBootstrapServers = originalProps.getProperty(Constants.BOOTSTRAP_SERVERS.key);
                    LOG.info("CONSUL: \"bootstrap.servers\" property value was: [" + originalBootstrapServers + "]");
                    originalProps.setProperty(Constants.BOOTSTRAP_SERVERS.key, bootstrapServers);
                    LOG.info("CONSUL: \"bootstrap.servers\" property value has been set to: [" + bootstrapServers + "]");
                }
            }
        } else {
            LOG.info("CONSUL: Not found required properties for using Consul service discovery");
        }
    }

    ArrayList<String> getBootStrapServersFromConsul(String consulServer, String consulKafkaServicename) {
        ArrayList<String> bootstrapServers = null;
        LOG.info("CONSUL: Consul server used: [" + consulServer + "]");
        LOG.info("CONSUL: Consul service retrieved: [" + consulKafkaServicename + "]");
        try {
            Consul consul = Consul.builder().withUrl(consulServer).build();
            HealthClient healthClient = consul.healthClient();

            // discover only "passing" nodes
            List<ServiceHealth> nodes = healthClient.getHealthyServiceInstances(consulKafkaServicename).getResponse();
            for (ServiceHealth kafkaNode : nodes) {
                String address = kafkaNode.getService().getAddress();
                String port = kafkaNode.getService().getPort() + "";
                if (bootstrapServers == null) {
                    bootstrapServers = new ArrayList<String>();
                }
                bootstrapServers.add(address + ":" + port);
            }
        } catch (Exception e){
            LOG.error("CONSUL: " + e.toString());
        }
        LOG.info("CONSUL: Kafka services found through Consul server: " + (bootstrapServers==null?"None available":bootstrapServers.toString()));
        return bootstrapServers;
    }

    public Pattern getTopicsPattern() {
        return Pattern.compile(props.getProperty(Constants.KAFKA_CONSUMER_TOPICS.key));
    }
    
    public Properties getProperties() {
        return props;
    }

    public long getMetricExpire() {
        return (long)Long.parseLong(get(Constants.EXPORTER_METRIC_EXPIRE.key, "0"));
    }

    public int getExporterPort() {
        return(getInt(PropertyConfig.Constants.EXPORTER_PORT.key, 9185));
    }
    
    public String get(String key) {
        String value = props.getProperty(key);
        if (value == null)
            throw new RuntimeException(key + " parameter not found in the configuration");
        return value;
    }

    public String get(String key, String defaultValue) {
        String value = props.getProperty(key);
        if (value == null)
            return defaultValue;
        return value;
    }

    public void set(String key, String value) {
        props.setProperty(key, value);
    }
    
    public int getInt(String key) {
        return (int)Long.parseLong(get(key));
    }

    public int getInt(String key, int defaultValue) {
        String value = props.getProperty(key);
        if (value == null)
            return defaultValue;
        return (int)Long.parseLong(get(key));
    }

    public boolean getBoolean(String key) {
        return Boolean.valueOf(get(key)).booleanValue();
    }

    public boolean getBoolean(String key, boolean defaultValue) {
        String value = props.getProperty(key);
        if (value == null)
            return defaultValue;
        return Boolean.valueOf(value).booleanValue();
    }


    private Properties loadProperties(String propFilePath) throws IOException {
        Properties props = new Properties();
        InputStream input = null;

        try {
            input = new FileInputStream(propFilePath);
            if (input != null) {
                props.load(input);
            } else {
                throw new FileNotFoundException(propFilePath + "' not found");
            }
        } finally {
            input.close();
        }

        return props;
    }

}