package com.netflix.exhibitor.core.config.azure;

import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.blob.BlobProperties;
import com.microsoft.azure.storage.blob.CloudBlob;
import com.netflix.exhibitor.core.azure.*;
import com.netflix.exhibitor.core.config.*;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Properties;

public class AzureConfigProvider implements ConfigProvider {
    private final AzureConfigArguments arguments;
    private final AzureClient azureClient;
    private final String hostname;
    private final Properties defaults;

    public static final String AUTO_GENERATED_NOTE = "Auto-generated by Exhibitor on %s";
    public static final int HTTP_FORBIDDEN = 403;
    public static final int HTTP_NOT_FOUND = 404;

    /**
     * @param factory     the factory
     * @param credentials credentials
     * @param arguments   args
     * @param hostname    this VM's hostname
     * @throws Exception errors
     */
    public AzureConfigProvider(AzureClientFactory factory, AzureCredential credentials, AzureConfigArguments arguments,
                               String hostname) throws Exception {
        this(factory, credentials, arguments, hostname, new Properties());
    }

    /**
     * @param factory     the factory
     * @param credentials credentials
     * @param arguments   args
     * @param hostname    this VM's hostname
     * @param defaults    default props
     * @throws Exception errors
     */
    public AzureConfigProvider(AzureClientFactory factory, AzureCredential credentials, AzureConfigArguments arguments,
                               String hostname, Properties defaults) throws Exception {
        this.arguments = arguments;
        this.hostname = hostname;
        this.defaults = defaults;
        azureClient = factory.makeNewClient(credentials);
    }

    public AzureClient getAzureClient() {
        return azureClient;
    }

    @Override
    public void start() throws Exception {
        // NOP
    }

    @Override
    public void close() throws IOException {
        azureClient.close();
    }

    @Override
    public PseudoLock newPseudoLock() throws Exception {
        return new AzurePseudoLock
                (
                        azureClient,
                        arguments.getContainer(),
                        arguments.getLockArguments().getPrefix(),
                        arguments.getLockArguments().getTimeoutMs(),
                        arguments.getLockArguments().getPollingMs(),
                        arguments.getLockArguments().getSettlingMs()
                );
    }

    @Override
    public LoadedInstanceConfig loadConfig() throws Exception {
        Date lastModified;
        Properties properties = new Properties();
        CloudBlob configObject = getConfigObject();
        if (configObject != null) {
            lastModified = configObject.getProperties().getLastModified();
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            configObject.download(out);
            properties.load(new ByteArrayInputStream(out.toByteArray()));
        } else {
            lastModified = new Date(0L);
        }

        PropertyBasedInstanceConfig config = new PropertyBasedInstanceConfig(properties, defaults);
        return new LoadedInstanceConfig(config, lastModified.getTime());
    }

    @Override
    public LoadedInstanceConfig storeConfig(ConfigCollection config, long compareVersion) throws Exception {
        {
            BlobProperties blobProperties = getConfigBlobProperties();
            if (blobProperties != null) {
                Date lastModified = blobProperties.getLastModified();
                if (lastModified.getTime() != compareVersion) {
                    return null;    // pattern copied from S3ConfigProvider - Azure may support a better way
                }
            }
        }

        PropertyBasedInstanceConfig propertyBasedInstanceConfig = new PropertyBasedInstanceConfig(config);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        propertyBasedInstanceConfig.getProperties().store(out, String.format(AUTO_GENERATED_NOTE, hostname));

        byte[] bytes = out.toByteArray();
        azureClient.putBlob(bytes, arguments.getContainer(), arguments.getBlobName());

        BlobProperties blobProperties = azureClient.getBlobProperties(arguments.getContainer(), arguments.getBlobName());
        return new LoadedInstanceConfig(propertyBasedInstanceConfig, blobProperties.getLastModified().getTime());
    }

    private BlobProperties getConfigBlobProperties() throws Exception {
        try {
            BlobProperties properties = azureClient.getBlobProperties(arguments.getContainer(), arguments.getBlobName());
            if (properties.getLength() > 0) {
                return properties;
            }
        } catch (StorageException e) {
            if (!isNotFoundError(e) && !isForbiddenError(e)) {
                throw e;
            }
        }
        return null;
    }

    private CloudBlob getConfigObject() throws Exception {
        try {
            CloudBlob object = azureClient.getBlob(arguments.getContainer(), arguments.getBlobName());
            object.downloadAttributes();
            if (object.getProperties().getLength() > 0) {
                return object;
            }
        } catch (StorageException e) {
            if (!isNotFoundError(e) && !isForbiddenError(e)) {
                throw e;
            }
        }
        return null;
    }

    private boolean isForbiddenError(StorageException e) {
        return ((e.getHttpStatusCode() == HTTP_FORBIDDEN));
    }

    private boolean isNotFoundError(StorageException e) {
        return (e.getHttpStatusCode() == HTTP_NOT_FOUND);
    }

}