package com.atlassian.bitbucket.mesh.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.core.env.Environment;

import javax.annotation.Nonnull;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.Duration;
import java.time.temporal.ChronoUnit;

import static org.apache.commons.lang3.StringUtils.*;

class DefaultNodeConfig implements NodeConfig {

    private static final Logger log = LoggerFactory.getLogger(NodeConfig.class);

    private final Environment environment;

    private volatile Settings current;

    DefaultNodeConfig(Environment environment) {
        this.environment = environment;
        reload();
    }

    @Nonnull
    @Override
    public Duration getConfigTimeout() {
        return current.configTimeout;
    }

    @Nonnull
    @Override
    public String getId() {
        return current.id;
    }

    @Nonnull
    @Override
    public String getName() {
        return current.name;
    }

    @Nonnull
    @Override
    public String getRpcUrl() {
        return current.rpcUrl;
    }

    @Override
    public int getSidebandQueueLimit() {
        return current.sidebandQueueLimit;
    }

    @Override
    public boolean isConfigRequired() {
        return current.configRequired;
    }

    void reload() {
        Settings previous = current;
        current = Binder.get(environment).bindOrCreate("node", Settings.class);

        if (previous != null) {
            String previousId = previous.id;
            String currentId = current.id;
            if (!previousId.equals(UNCONFIGURED_NODE_ID) && !previousId.equals(currentId)) {
                log.warn("Node ID changed from {} to {}", previousId, currentId);
            }
        }
    }

    private static class Settings {

        final boolean configRequired;
        final Duration configTimeout;
        final String id;
        final String name;
        final String rpcUrl;
        final int sidebandQueueLimit;

        @ConstructorBinding
        public Settings(@DefaultValue("true") boolean configRequired,
                        @DefaultValue("15s") @DurationUnit(ChronoUnit.SECONDS) Duration configTimeout,
                        @DefaultValue("") String id,
                        @DefaultValue("") String name,
                        @DefaultValue("http://localhost:${grpc.server.port:7777}") String rpcUrl,
                        @DefaultValue("1024") int sidebandQueueLimit) {
            this.configRequired = configRequired;
            this.configTimeout = configTimeout;
            this.id = defaultString(trimToNull(id), UNCONFIGURED_NODE_ID);
            this.name = isNotBlank(name) ? name : getHostName();
            this.rpcUrl = rpcUrl;
            this.sidebandQueueLimit = sidebandQueueLimit;
        }

        private static String getHostName() {
            try {
                return InetAddress.getLocalHost().getHostName();
            } catch (UnknownHostException e) {
                return "";
            }
        }
    }
}
