// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

namespace Microsoft.AspNetCore.DataProtection.Repositories
{
    internal sealed class DefaultKeyStorageDirectories : IDefaultKeyStorageDirectories
    {
        private static readonly Lazy<DirectoryInfo> _defaultDirectoryLazy = new Lazy<DirectoryInfo>(GetKeyStorageDirectoryImpl);

        private DefaultKeyStorageDirectories()
        {
        }

        public static IDefaultKeyStorageDirectories Instance { get; } = new DefaultKeyStorageDirectories();

        /// <summary>
        /// The default key storage directory.
        /// On Windows, this currently corresponds to "Environment.SpecialFolder.LocalApplication/ASP.NET/DataProtection-Keys".
        /// On Linux and macOS, this currently corresponds to "$HOME/.aspnet/DataProtection-Keys".
        /// </summary>
        /// <remarks>
        /// This property can return null if no suitable default key storage directory can
        /// be found, such as the case when the user profile is unavailable.
        /// </remarks>
        public DirectoryInfo GetKeyStorageDirectory() => _defaultDirectoryLazy.Value;

        private static DirectoryInfo GetKeyStorageDirectoryImpl()
        {
            DirectoryInfo retVal;

            // Environment.GetFolderPath returns null if the user profile isn't loaded.
            var localAppDataFromSystemPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            var localAppDataFromEnvPath = Environment.GetEnvironmentVariable("LOCALAPPDATA");
            var userProfilePath = Environment.GetEnvironmentVariable("USERPROFILE");
            var homePath = Environment.GetEnvironmentVariable("HOME");

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !string.IsNullOrEmpty(localAppDataFromSystemPath))
            {
                // To preserve backwards-compatibility with 1.x, Environment.SpecialFolder.LocalApplicationData
                // cannot take precedence over $LOCALAPPDATA and $HOME/.aspnet on non-Windows platforms
                retVal = GetKeyStorageDirectoryFromBaseAppDataPath(localAppDataFromSystemPath);
            }
            else if (localAppDataFromEnvPath != null)
            {
                retVal = GetKeyStorageDirectoryFromBaseAppDataPath(localAppDataFromEnvPath);
            }
            else if (userProfilePath != null)
            {
                retVal = GetKeyStorageDirectoryFromBaseAppDataPath(Path.Combine(userProfilePath, "AppData", "Local"));
            }
            else if (homePath != null)
            {
                // If LOCALAPPDATA and USERPROFILE are not present but HOME is,
                // it's a good guess that this is a *NIX machine.  Use *NIX conventions for a folder name.
                retVal = new DirectoryInfo(Path.Combine(homePath, ".aspnet", DataProtectionKeysFolderName));
            }
            else if (!string.IsNullOrEmpty(localAppDataFromSystemPath))
            {
                // Starting in 2.x, non-Windows platforms may use Environment.SpecialFolder.LocalApplicationData
                // but only after checking for $LOCALAPPDATA, $USERPROFILE, and $HOME.
                retVal = GetKeyStorageDirectoryFromBaseAppDataPath(localAppDataFromSystemPath);
            }
            else
            {
                return null;
            }

            Debug.Assert(retVal != null);

            try
            {
                retVal.Create(); // throws if we don't have access, e.g., user profile not loaded
                return retVal;
            }
            catch
            {
                return null;
            }
        }

        public DirectoryInfo GetKeyStorageDirectoryForAzureWebSites()
        {
            // Azure Web Sites needs to be treated specially, as we need to store the keys in a
            // correct persisted location. We use the existence of the %WEBSITE_INSTANCE_ID% env
            // variable to determine if we're running in this environment, and if so we then use
            // the %HOME% variable to build up our base key storage path.
            if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID")))
            {
                var homeEnvVar = Environment.GetEnvironmentVariable("HOME");
                if (!String.IsNullOrEmpty(homeEnvVar))
                {
                    return GetKeyStorageDirectoryFromBaseAppDataPath(homeEnvVar);
                }
            }

            // nope
            return null;
        }

        private const string DataProtectionKeysFolderName = "DataProtection-Keys";

        private static DirectoryInfo GetKeyStorageDirectoryFromBaseAppDataPath(string basePath)
        {
            return new DirectoryInfo(Path.Combine(basePath, "ASP.NET", DataProtectionKeysFolderName));
        }
    }
}
