using Amazon;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Util;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
using AWSSDK_DotNet.IntegrationTests.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net;
using System.Text.RegularExpressions;

namespace AWSSDK_DotNet.IntegrationTests.Tests.S3
{
    [TestClass]
    public class BucketRegionTests
    {
        [TestMethod]
        [TestCategory("S3")]
        public void HappyCaseSigV2()
        {
            // make sure we're not using the cache with SigV2 requests
            using (var runner = new BucketRegionTestRunner(false))
            {
                if (runner.TestBucketIsReady)
                {
                    runner.USEast1Client.PutObject(runner.PutObjectRequest);
                    RegionEndpoint cachedRegion;
                    Assert.IsFalse(BucketRegionDetector.BucketRegionCache.TryGetValue(runner.BucketName, out cachedRegion));
                    Assert.AreEqual(null, cachedRegion);
                }
            }
        }

        [TestMethod]
        [TestCategory("S3")]
        public void HappyCaseSigV4()
        {
            // make sure the cache works when it gets the region from the response body
            using (var runner = new BucketRegionTestRunner(true))
            {
                if (runner.TestBucketIsReady)
                {
                    runner.USEast1Client.PutObject(runner.PutObjectRequest);
                    RegionEndpoint cachedRegion;
                    Assert.IsTrue(BucketRegionDetector.BucketRegionCache.TryGetValue(runner.BucketName, out cachedRegion));
                    Assert.AreEqual(RegionEndpoint.USWest1, cachedRegion);
                }
            }
        }

        [TestMethod]
        [TestCategory("S3")]
        public void HappyCaseGetObjectMetedata()
        {
            // make sure the cache works when it gets the region from a HEAD bucket request
            using (var runner = new BucketRegionTestRunner(true))
            {
                if (runner.TestBucketIsReady)
                {
                    // ensure the object exists then clear the cache
                    runner.USEast1Client.PutObject(runner.PutObjectRequest);
                    BucketRegionDetector.BucketRegionCache.Clear();
                    runner.USEast1Client.GetObjectMetadata(runner.GetObjectMetadataRequest);
                    RegionEndpoint cachedRegion;
                    Assert.IsTrue(BucketRegionDetector.BucketRegionCache.TryGetValue(runner.BucketName, out cachedRegion));
                    Assert.AreEqual(RegionEndpoint.USWest1, cachedRegion);
                }
            }
        }

        [TestMethod]
        [TestCategory("S3")]
        [TestCategory("RequiresIAMUser")]
        public void GetObjectMetedataSessionCredentials()
        {
            // make sure the cache works when it gets the region from a HEAD bucket request
            using (var runner = new BucketRegionTestRunner(true, setupClientWithSessionCredentials: true))
            {
                if (runner.TestBucketIsReady)
                {
                    // ensure the object exists then clear the cache
                    runner.USEast1ClientWithSessionCredentials.PutObject(runner.PutObjectRequest);
                    BucketRegionDetector.BucketRegionCache.Clear();
                    runner.USEast1ClientWithSessionCredentials.GetObjectMetadata(runner.GetObjectMetadataRequest);
                    RegionEndpoint cachedRegion;
                    Assert.IsTrue(BucketRegionDetector.BucketRegionCache.TryGetValue(runner.BucketName, out cachedRegion));
                    Assert.AreEqual(RegionEndpoint.USWest1, cachedRegion);
                }
            }
        }

        [TestMethod]
        [TestCategory("S3")]
        public void HappyCaseDoesS3BucketExist()
        {
            // make sure the cache works when it gets the region from the x-amz-bucket-region header
            using (var runner = new BucketRegionTestRunner(true, true))
            {
                if (runner.TestBucketIsReady)
                {
                    Assert.IsTrue(AmazonS3Util.DoesS3BucketExistV2(runner.USEast1Client, runner.BucketName));
                    RegionEndpoint cachedRegion;
                    Assert.IsTrue(BucketRegionDetector.BucketRegionCache.TryGetValue(runner.BucketName, out cachedRegion));
                    Assert.AreEqual(RegionEndpoint.USWest1, cachedRegion);
                }
            }
        }

        [TestMethod]
        [TestCategory("S3")]
        public void BucketRecreatedInDifferentRegion()
        {
            // make sure the cache gets refreshed when it should
            using (var runner = new BucketRegionTestRunner(true))
            {
                if (runner.TestBucketIsReady)
                {
                    BucketRegionDetector.BucketRegionCache.AddOrUpdate(runner.BucketName, RegionEndpoint.APNortheast2);
                    runner.USEast1Client.PutObject(runner.PutObjectRequest);
                    RegionEndpoint cachedRegion;
                    Assert.IsTrue(BucketRegionDetector.BucketRegionCache.TryGetValue(runner.BucketName, out cachedRegion));
                    Assert.AreEqual(RegionEndpoint.USWest1, cachedRegion);
                }
            }
        }

        [TestMethod]
        [TestCategory("S3")]
        public void GetPreSignedUrlSigV2()
        {
            using (var runner = new BucketRegionTestRunner(false))
            {
                if (runner.TestBucketIsReady)
                {
                    Assert.AreEqual(HttpStatusCode.OK, GetHttpStatusCode(runner.USEast1Client.GetPreSignedURL(runner.PreSignedUrlRequest)));
                }
            }
        }

        [TestMethod]
        [TestCategory("S3")]
        public void GetPreSignedUrlSigV4ImplicitlySet()
        {
            using (var runner = new BucketRegionTestRunner(true))
            {
                if (runner.TestBucketIsReady)
                {
                    Assert.AreEqual(HttpStatusCode.OK, GetHttpStatusCode(runner.USEast1Client.GetPreSignedURL(runner.PreSignedUrlRequest)));
                }
            }
        }

        [TestMethod]
        [TestCategory("S3")]
        public void GetPreSignedUrlSigV4ExplicitlySet()
        {
            using (var runner = new BucketRegionTestRunner(true, true))
            {
                if (runner.TestBucketIsReady)
                {
                    Assert.AreEqual(HttpStatusCode.BadRequest, GetHttpStatusCode(runner.USEast1Client.GetPreSignedURL(runner.PreSignedUrlRequest)));
                }
            }
        }

        [TestMethod]
        [TestCategory("S3")]
        public void NoCredentialsOnContext()
        {
            var request = new AssumeRoleRequest()
            {
                DurationSeconds = 0 // invalid value for DurationSeconds - credentials will be null when retrying request
            };
            // keep this unit test even though STSAssumeRoleAWSCredentials is obsolete
            // the condition it's testing doesn't exist when AssumeRoleAWSCredentials (STSAssumeRoleAWSCredentials's replacement) is used
#pragma warning disable 0618
            var credentials = new STSAssumeRoleAWSCredentials(new AmazonSecurityTokenServiceClient(), request);
            var s3Client = new AmazonS3Client(credentials);
            AssertExtensions.ExpectException(() => { s3Client.ListBuckets(); }, typeof(AmazonSecurityTokenServiceException), new Regex("3 validation errors detected"));
        }

        [TestMethod]
        [TestCategory("S3")]
        public void DeleteBucketUsingS3RegionUSEast1Enum()
        {
            using (var runner = new BucketRegionTestRunner(true, true))
            {
                var bucketName = S3TestUtils.CreateBucketWithWait(runner.USEast1Client);

                runner.USWest1Client.DeleteBucket(new DeleteBucketRequest
                {
                    BucketName = bucketName,
                    BucketRegion = S3Region.US
                });
            }
        }


        private HttpStatusCode GetHttpStatusCode(string url)
        {
            var httpRequest = WebRequest.Create(url) as HttpWebRequest;
            try
            {
                using (var httpResponse = httpRequest.GetResponse() as HttpWebResponse)
                {
                    return httpResponse.StatusCode;
                }
            }
            catch (WebException we)
            {
                using (var errorResponse = we.Response as HttpWebResponse)
                {
                    return errorResponse.StatusCode;
                }
            }
        }
    }
}