/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using Amazon.Runtime.Internal.Util;
using Amazon.Util;
using Amazon.Util.Internal;

namespace Amazon.Runtime.Internal
{
    /// <inheritdoc cref="GetDefaultConfiguration"/>
    public interface IDefaultConfigurationProvider
    {
        /// <summary>
        /// Determines the correct <see cref="DefaultConfiguration"/> to use.
        /// <para />
        /// Implementations of <see cref="IDefaultConfigurationProvider"/> are responsible for storing a reference to
        /// all valid <see cref="DefaultConfiguration"/>s for a given context.  Because the default values in a <see cref="DefaultConfiguration"/>
        /// can differ between Services, it's important to not use <see cref="IDefaultConfigurationProvider"/>s in a different
        /// Service Client.
        /// <para />
        /// The <see cref="DefaultConfiguration"/> is selected as follows:
        /// <list type="number">
        /// <item>Mode matching <paramref name="requestedConfigurationMode"/>.  This should be set via <see cref="ClientConfig.DefaultConfigurationMode"/></item>
        /// <item>The Environment Variable <see cref="DefaultConfigurationProvider.AWS_DEFAULTS_MODE_ENVIRONMENT_VARIABLE"/></item>
        /// <item>Shared config/credential file via <see cref="FallbackInternalConfigurationFactory.DefaultConfigurationModeName"/></item>
        /// <item><see cref="DefaultConfigurationMode.Legacy"/></item>
        /// </list>
        /// </summary>
        /// <remarks>
        /// <see cref="IDefaultConfiguration"/> is not cached.  It will be re-resolved on every call.
        /// </remarks>
        IDefaultConfiguration GetDefaultConfiguration(RegionEndpoint clientRegion, DefaultConfigurationMode? requestedConfigurationMode = null);
    }

    /// <inheritdoc cref="IDefaultConfigurationProvider"/>
    public class DefaultConfigurationProvider : IDefaultConfigurationProvider
    {
        private const string AWS_DEFAULTS_MODE_ENVIRONMENT_VARIABLE = "AWS_DEFAULTS_MODE";

        private readonly IEnvironmentVariableRetriever _environmentVariableRetriever;
        private readonly IDefaultConfigurationAutoModeResolver _defaultConfigurationAutoModeResolver;

        private readonly IDefaultConfiguration[] _availableConfigurations;

        /// <inheritdoc cref="IDefaultConfigurationProvider"/>
        public DefaultConfigurationProvider(IEnumerable<IDefaultConfiguration> availableConfigurations)
            : this(EnvironmentVariableSource.Instance.EnvironmentVariableRetriever,
                new DefaultConfigurationAutoModeResolver(
                    new RuntimeInformationProvider(),
                    EnvironmentVariableSource.Instance.EnvironmentVariableRetriever),
                availableConfigurations) { }

        /// <inheritdoc cref="IDefaultConfigurationProvider"/>
        public DefaultConfigurationProvider(
            IEnvironmentVariableRetriever environmentVariableRetriever,
            IDefaultConfigurationAutoModeResolver defaultConfigurationAutoModeResolver,
            IEnumerable<IDefaultConfiguration> availableConfigurations)
            : this (environmentVariableRetriever, defaultConfigurationAutoModeResolver, availableConfigurations.ToArray()) { }

        /// <inheritdoc cref="IDefaultConfigurationProvider"/>
        public DefaultConfigurationProvider(
            IEnvironmentVariableRetriever environmentVariableRetriever,
            IDefaultConfigurationAutoModeResolver defaultConfigurationAutoModeResolver,
            params IDefaultConfiguration[] availableConfigurations)
        {
            if (availableConfigurations?.Any() != true)
                throw new ArgumentException(
                    "Must provide at least one Default Configuration",
                    nameof(availableConfigurations));

            _environmentVariableRetriever = environmentVariableRetriever;
            _defaultConfigurationAutoModeResolver = defaultConfigurationAutoModeResolver;
            _availableConfigurations = availableConfigurations;
        }

        /// <inheritdoc cref="IDefaultConfigurationProvider"/>
        public IDefaultConfiguration GetDefaultConfiguration(
            RegionEndpoint clientRegion,
            DefaultConfigurationMode? requestedConfigurationMode = null)
        {
            var defaultConfigurationModeName =
                // 1) requested mode
                requestedConfigurationMode?.ToString() ??
                // 2) try to get from environment variable
                _environmentVariableRetriever.GetEnvironmentVariable(AWS_DEFAULTS_MODE_ENVIRONMENT_VARIABLE) ??
                // 3) try to get from shared config/credential file
                FallbackInternalConfigurationFactory.DefaultConfigurationModeName ??
                // 4) fallback to 'Legacy'
                DefaultConfigurationMode.Legacy.ToString();

            Logger
                .GetLogger(GetType())
                .InfoFormat(
                    $"Resolved {nameof(DefaultConfigurationMode)} for {nameof(RegionEndpoint)} [{clientRegion?.SystemName}] to [{defaultConfigurationModeName}].");

            try
            {
                var mode =
                    (DefaultConfigurationMode)
                    Enum.Parse(typeof(DefaultConfigurationMode), defaultConfigurationModeName, ignoreCase: true);

                if (mode == DefaultConfigurationMode.Auto)
                    mode = _defaultConfigurationAutoModeResolver.Resolve(clientRegion, () => EC2InstanceMetadata.Region);
               
                // save resolved values to cache
                return _availableConfigurations.First(x => x.Name == mode);
            }
            catch (Exception)
            {
                throw new AmazonClientException(
                    $"Failed to find requested Default Configuration Mode '{defaultConfigurationModeName}'.  " +
                    $"Valid values are {string.Join(",", Enum.GetNames(typeof(DefaultConfigurationMode)))}");
            }
        }
    }
}