// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Net.Http;
using Microsoft.Build.Framework;
using Task = Microsoft.Build.Utilities.Task;
namespace Microsoft.DotNet.Build.CloudTestTasks
{
    public sealed class CreateAzureContainer : Task
    {
        /// 
        /// The Azure account key used when creating the connection string.
        /// 
        [Required]
        public string AccountKey { get; set; }
        /// 
        /// The Azure account name used when creating the connection string.
        /// 
        [Required]
        public string AccountName { get; set; }
        /// 
        /// The name of the container to create.  The specified name must be in the correct format, see the
        /// following page for more info.  https://msdn.microsoft.com/en-us/library/azure/dd135715.aspx
        /// 
        [Required]
        public string ContainerName { get; set; }
        /// 
        /// When false, if the specified container already exists get a reference to it.
        /// When true, if the specified container already exists the task will fail.
        /// 
        public bool FailIfExists { get; set; }
        /// 
        /// The read-only SAS token created when ReadOnlyTokenDaysValid is greater than zero.
        /// 
        [Output]
        public string ReadOnlyToken { get; set; }
        /// 
        /// The number of days for which the read-only token should be valid.
        /// 
        public int ReadOnlyTokenDaysValid { get; set; }
        /// 
        /// The URI of the created container.
        /// 
        [Output]
        public string StorageUri { get; set; }
        /// 
        /// The write-only SAS token create when WriteOnlyTokenDaysValid is greater than zero.
        /// 
        [Output]
        public string WriteOnlyToken { get; set; }
        /// 
        /// The number of days for which the write-only token should be valid.
        /// 
        public int WriteOnlyTokenDaysValid { get; set; }
        public override bool Execute()
        {
            return ExecuteAsync().GetAwaiter().GetResult();
        }
        public async Task ExecuteAsync()
        {
            Log.LogMessage(
                MessageImportance.High,
                "Creating container named '{0}' in storage account {1}.",
                ContainerName,
                AccountName);
            string url = string.Format(
                "https://{0}.blob.core.windows.net/{1}?restype=container",
                AccountName,
                ContainerName);
            StorageUri = string.Format(
                "https://{0}.blob.core.windows.net/{1}/",
                AccountName,
                ContainerName);
            Log.LogMessage(MessageImportance.Low, "Sending request to create Container");
            using (HttpClient client = new HttpClient())
            {
                Func createRequest = () =>
                {
                    DateTime dt = DateTime.UtcNow;
                    var req = new HttpRequestMessage(HttpMethod.Put, url);
                    req.Headers.Add(AzureHelper.DateHeaderString, dt.ToString("R", CultureInfo.InvariantCulture));
                    req.Headers.Add(AzureHelper.VersionHeaderString, AzureHelper.StorageApiVersion);
                    req.Headers.Add(AzureHelper.AuthorizationHeaderString, AzureHelper.AuthorizationHeader(
                            AccountName,
                            AccountKey,
                            "PUT",
                            dt,
                            req));
                    byte[] bytestoWrite = new byte[0];
                    int bytesToWriteLength = 0;
                    Stream postStream = new MemoryStream();
                    postStream.Write(bytestoWrite, 0, bytesToWriteLength);
                    req.Content = new StreamContent(postStream);
                    return req;
                };
                Func validate = (HttpResponseMessage response) =>
                {
                    // the Conflict status (409) indicates that the container already exists, so
                    // if FailIfExists is set to false and we get a 409 don't fail the task.
                    return response.IsSuccessStatusCode || (!FailIfExists && response.StatusCode == HttpStatusCode.Conflict);
                };
                using (HttpResponseMessage response = await AzureHelper.RequestWithRetry(Log, client, createRequest, validate))
                {
                    try
                    {
                        Log.LogMessage(
                            MessageImportance.Low,
                            "Received response to create Container {0}: Status Code: {1} {2}",
                            ContainerName, response.StatusCode, response.Content.ToString());
                        // specifying zero is valid, it means "I don't want a token"
                        if (ReadOnlyTokenDaysValid > 0)
                        {
                            ReadOnlyToken = AzureHelper.CreateContainerSasToken(
                                AccountName,
                                ContainerName,
                                AccountKey,
                                AzureHelper.SasAccessType.Read,
                                ReadOnlyTokenDaysValid);
                        }
                        // specifying zero is valid, it means "I don't want a token"
                        if (WriteOnlyTokenDaysValid > 0)
                        {
                            WriteOnlyToken = AzureHelper.CreateContainerSasToken(
                                AccountName,
                                ContainerName,
                                AccountKey,
                                AzureHelper.SasAccessType.Write,
                                WriteOnlyTokenDaysValid);
                        }
                    }
                    catch (Exception e)
                    {
                        Log.LogErrorFromException(e, true);
                        return false;
                    }
                }
            }
            return true;
        }
    }
}