package com.atlassian.bitbucket.internal.mirroring.mirror;

import com.atlassian.bitbucket.mirroring.mirror.UpstreamServerType;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.google.common.primitives.Ints;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static com.atlassian.bitbucket.internal.mirroring.mirror.MirrorConstants.*;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.slf4j.LoggerFactory.getLogger;

@Component("mirroringConfig")
public class DefaultMirroringConfig implements MirroringConfig {

    private static final Logger log = getLogger(DefaultMirroringConfig.class);
    
    private final ApplicationPropertiesService propertiesService;

    @Autowired
    public DefaultMirroringConfig(ApplicationPropertiesService propertiesService) {
        this.propertiesService = propertiesService;
    }

    @Nonnull
    @Override
    public String getAddonDescription() {
        return "Smart Mirroring service - provides fast access to repositories.";
    }

    @Nonnull
    @Override
    public String getAddonKey() {
        return "bitbucket.mirror." + requireNonNull(propertiesService.getServerId(), "serverId").toLowerCase(Locale.ROOT);
    }

    @Nonnull
    @Override
    public String getAddonName() {
        return "Atlassian Bitbucket Mirror - " + getDisplayName();
    }

    @Nonnull
    @Override
    public String getAddonStpModuleName() {
        return "Support Tools - " + getDisplayName();
    }

    @Nonnull
    @Override
    public Optional<String> getCloudUpstreamUrl() {
        return ofNullable(propertiesService.getPluginProperty(PROPERTY_CLOUD_URL, null));
    }

    @Nonnull
    @Override
    public String getDisplayName() {
        String displayName = propertiesService.getDisplayName();
        checkState(displayName != null, "displayName");

        return displayName;
    }

    @Override
    public int getMaxSyncFailedCount() {
        return getPositiveValue(PROPERTY_SYNC_MAX_FAILURES, DEFAULT_SYNC_MAX_FAILURES);
    }

    @Override
    public int getMaxSyncThreadCount() {
        return getPositiveValue(PROPERTY_SYNC_THREADS, DEFAULT_SYNC_THREADS);
    }

    @Override
    public int getUpstreamRepositoryRequestPageSize() {
        return getPositiveValue(PROPERTY_SYNC_REPOS_PAGE_SIZE_MAX, DEFAULT_SYNC_REPO_REQUEST_MAX_PAGE);
    }

    @Nonnull
    @Override
    public Optional<String> getServerUpstreamUrl() {
        return ofNullable(propertiesService.getPluginProperty(PROPERTY_UPSTREAM_URL, null));
    }

    @Override
    public long getStateRefreshInterval(@Nonnull TimeUnit timeUnit) {
        return requireNonNull(timeUnit, "timeUnit").convert(
                getPositiveValue(PROPERTY_STATE_REFRESH_INTERVAL, DEFAULT_REFRESH_INTERVAL_MINUTES),
                MINUTES);
    }

    @Override
    public long getSyncExecTimeout(@Nonnull TimeUnit timeUnit) {
        return requireNonNull(timeUnit, "timeUnit").convert(
                getPositiveValue(PROPERTY_SYNC_FETCH_TIMEOUT_EXEC, DEFAULT_SYNC_TIMEOUT_EXEC_SECONDS),
                SECONDS);
    }

    @Override
    public long getSyncInitialDelay(@Nonnull TimeUnit timeUnit) {
        return requireNonNull(timeUnit, "timeUnit").convert(
                getPositiveValue(PROPERTY_SYNC_DELAY_INITIAL, DEFAULT_SYNC_DELAY_SECONDS),
                SECONDS);
    }

    @Override
    public long getSyncIdleTimeout(@Nonnull TimeUnit timeUnit) {
        return requireNonNull(timeUnit, "timeUnit").convert(
                getPositiveValue(PROPERTY_SYNC_FETCH_TIMEOUT_IDLE, DEFAULT_SYNC_TIMEOUT_IDLE_SECONDS),
                SECONDS);
    }

    @Override
    public long getSyncInterval(@Nonnull TimeUnit timeUnit) {
        return requireNonNull(timeUnit, "timeUnit").convert(
                getPositiveValue(PROPERTY_SYNC_INTERVAL, DEFAULT_SYNC_INTERVAL_MINUTES),
                MINUTES);
    }

    @Nonnull
    @Override
    public Optional<String> getUpstreamNamespace() {
        return ofNullable(propertiesService.getPluginProperty(PROPERTY_UPSTREAM_NAMESPACE, null));
    }

    @Nonnull
    @Override
    public Optional<UpstreamServerType> getUpstreamType() {
        return UpstreamServerType.fromId(propertiesService.getPluginProperty(PROPERTY_UPSTREAM_TYPE, null));
    }

    private int getPositiveValue(String propName, int defaultValue) {
        return Ints.saturatedCast(getPositiveValue(propName, (long) defaultValue));
    }

    private long getPositiveValue(String propName, long defaultValue) {
        long value;
        try {
            value = propertiesService.getPluginProperty(propName, defaultValue);
            if (value <= 0) {
                log.warn("Property {} must be positive. Defaulting to {}", propName,
                        value, defaultValue);
                value = defaultValue;
            }
        } catch (IllegalArgumentException e) {  // Note: NumberFormatException is a subclass of IllegalArgumentException
            log.warn("Property {} has an invalid value. Defaulting to {}", propName,
                    defaultValue, e);
            value = defaultValue;
        }
        return value;
    }
}
