//-----------------------------------------------------------------------------
// 
//      Copyright 2016 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.Collections.Generic;
using System.Configuration;
using Amazon.XRay.Recorder.Core;
using Amazon.XRay.Recorder.Core.Internal.Entities;
using Amazon.XRay.Recorder.Core.Internal.Utils;
using Amazon.XRay.Recorder.Core.Plugins;
using Amazon.XRay.Recorder.Core.Sampling;
using Amazon.XRay.Recorder.Core.Strategies;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
using Amazon.XRay.Recorder.Core.Internal.Emitters;
using System;
using Amazon.XRay.Recorder.Core.Internal.Context;
using Amazon.XRay.Recorder.Core.Exceptions;
using Amazon.Runtime;
#if !NETFRAMEWORK
using Microsoft.Extensions.Configuration;
#endif
namespace Amazon.XRay.Recorder.UnitTests
{
    [TestClass]
    public class AwsXrayRecorderBuilderTests : TestBase
    {
        private const string PluginKey = "AWSXRayPlugins";
        private const string UseRuntimeErrors = "UseRuntimeErrors";
        private AWSXRayRecorder _recorder;
#if !NETFRAMEWORK
        private XRayOptions _xRayOptions = new XRayOptions();
#endif
        [TestInitialize]
        public void Initialize()
        {
            _recorder = new AWSXRayRecorder();
        }
        [TestCleanup]
        public new void TestCleanup()
        {
            base.TestCleanup();
#if NETFRAMEWORK
            ConfigurationManager.AppSettings[PluginKey] = null;
            ConfigurationManager.AppSettings[UseRuntimeErrors] = null;
            AppSettings.Reset();
#else
            _xRayOptions = new XRayOptions();
#endif
            _recorder.Dispose();
            AWSXRayRecorder.Instance.Dispose();
            _recorder = null;
        }
        [TestMethod]
        public void TestBuildWithDummyPlugin()
        {
            var dummyPlugin = new DummyPlugin();
            AWSXRayRecorder recorder = new AWSXRayRecorderBuilder().WithPlugin(dummyPlugin).Build();
            recorder.BeginSegment("test", TraceId);
            var segment = (Segment)AWSXRayRecorder.Instance.TraceContext.GetEntity();
            recorder.EndSegment();
            Assert.AreEqual("Origin", segment.Origin);
            var dict = segment.Aws[dummyPlugin.ServiceName] as Dictionary;
            Assert.AreEqual("value1", dict["key1"]);
        }
        [TestMethod]
        public void TestWithDefaultPlugins()
        {
#if NETFRAMEWORK
            ConfigurationManager.AppSettings[PluginKey] = "EC2Plugin";
            AppSettings.Reset();
            AWSXRayRecorderBuilder builder = new AWSXRayRecorderBuilder().WithPluginsFromAppSettings();
#else
            _xRayOptions.PluginSetting = "EC2Plugin";
            AWSXRayRecorderBuilder builder = new AWSXRayRecorderBuilder().WithPluginsFromConfig(_xRayOptions);
#endif
            Assert.AreEqual(1, builder.Plugins.Count);
            var expectedType = typeof(EC2Plugin);
            var actualType = builder.Plugins[0].GetType();
            Assert.AreEqual(expectedType, actualType);
        }
        [TestMethod]
        public void TestPluginSettingMissing()
        {
#if NETFRAMEWORK
            var builder = new AWSXRayRecorderBuilder().WithPluginsFromAppSettings();
#else
            var builder = new AWSXRayRecorderBuilder().WithPluginsFromConfig(_xRayOptions);
#endif
            Assert.AreEqual(0, builder.Plugins.Count);
        }
        [TestMethod]
        public void TestInvalidPluginSetting()
        {
#if NETFRAMEWORK
            ConfigurationManager.AppSettings[PluginKey] = "UnknownPlugin, IPlugin";
            AppSettings.Reset();
            AWSXRayRecorderBuilder builder = new AWSXRayRecorderBuilder().WithPluginsFromAppSettings();
#else
            _xRayOptions.PluginSetting = "UnknownPlugin, IPlugin";
            AWSXRayRecorderBuilder builder = new AWSXRayRecorderBuilder().WithPluginsFromConfig(_xRayOptions);
#endif
            Assert.AreEqual(0, builder.Plugins.Count);
        }
        [TestMethod]
        public void TestSetSamplingStrategy()
        {
            var recorder = new AWSXRayRecorderBuilder().WithSamplingStrategy(new DummySamplingStrategy()).Build();
            Assert.AreEqual(typeof(DummySamplingStrategy).FullName, recorder.SamplingStrategy.GetType().FullName);
        }
        [TestMethod]
        public void TestSetStreamingStrategy()
        {
            var recorder = new AWSXRayRecorderBuilder().WithStreamingStrategy(new DummyStreamingStrategy()).Build();
            Assert.AreEqual(typeof(DummyStreamingStrategy).FullName, recorder.StreamingStrategy.GetType().FullName);
        }
        [TestMethod]
        public void TestDefaultValueOfContextMissingStrategy()
        {
            var recorder = new AWSXRayRecorderBuilder().Build();
            Assert.AreEqual(ContextMissingStrategy.LOG_ERROR, recorder.ContextMissingStrategy);
        }
        [TestMethod]
        public void TestSetContextMissingStrategy()
        {
            var recorder = new AWSXRayRecorderBuilder().WithContextMissingStrategy(ContextMissingStrategy.LOG_ERROR).Build();
            Assert.AreEqual(ContextMissingStrategy.LOG_ERROR, recorder.ContextMissingStrategy);
        }
        [TestMethod]
        public void TestSetContextMissingUsingConfiguration1() // Contextmissing startegy set to log error from configuration
        {
#if NETFRAMEWORK
            ConfigurationManager.AppSettings[UseRuntimeErrors] = "false";
            AppSettings.Reset();
            AWSXRayRecorderBuilder builder = new AWSXRayRecorderBuilder().WithContextMissingStrategyFromAppSettings();
#else
            _xRayOptions.UseRuntimeErrors = false;
            AWSXRayRecorderBuilder builder = new AWSXRayRecorderBuilder().WithContextMissingStrategyFromConfig(_xRayOptions);
#endif
            AWSXRayRecorder recorder = builder.Build();
            Assert.AreEqual(ContextMissingStrategy.LOG_ERROR, recorder.ContextMissingStrategy);
        }
        [TestMethod]
        public void TestSetContextMissingUsingConfiguration2() // Contextmissing strategy not set
        {
#if NETFRAMEWORK
            AppSettings.Reset();
            AWSXRayRecorderBuilder builder = new AWSXRayRecorderBuilder().WithContextMissingStrategyFromAppSettings();
#else
            AWSXRayRecorderBuilder builder = new AWSXRayRecorderBuilder().WithContextMissingStrategyFromConfig(_xRayOptions);
#endif
            AWSXRayRecorder recorder = builder.Build();
            Assert.AreEqual(ContextMissingStrategy.LOG_ERROR, recorder.ContextMissingStrategy); // Default context missing strategy is set
        }
        [TestMethod]
        public void TestSetContextMissingUsingConfiguration3() // Contextmissing startegy is set through environment and configurations
        {
            Environment.SetEnvironmentVariable(AWSXRayRecorder.EnvironmentVariableContextMissingStrategy, "LOG_ERROR");
#if NETFRAMEWORK
            ConfigurationManager.AppSettings[UseRuntimeErrors] = "true";
            AppSettings.Reset();
            AWSXRayRecorderBuilder builder = new AWSXRayRecorderBuilder().WithContextMissingStrategyFromAppSettings();
#else
            _xRayOptions.UseRuntimeErrors = true;
            AWSXRayRecorderBuilder builder = new AWSXRayRecorderBuilder().WithContextMissingStrategyFromConfig(_xRayOptions);
#endif
            AWSXRayRecorder recorder = builder.Build();
            Assert.AreEqual(ContextMissingStrategy.LOG_ERROR, recorder.ContextMissingStrategy); // Preference given to environment variable
            Environment.SetEnvironmentVariable(AWSXRayRecorder.EnvironmentVariableContextMissingStrategy, null);
        }
        [TestMethod]
        public void TestSetEmitter()
        {
            var recorder = new AWSXRayRecorderBuilder().WithContextMissingStrategy(ContextMissingStrategy.LOG_ERROR).WithSegmentEmitter(new DummyEmitter()).Build();
            Assert.AreEqual(typeof(DummyEmitter).FullName, recorder.Emitter.GetType().FullName);
        }
        [TestMethod]
        public void TestSetDefaultEmitter()
        {
            var recorder = new AWSXRayRecorderBuilder().WithContextMissingStrategy(ContextMissingStrategy.LOG_ERROR).Build(); // set default UDP emitter
            Assert.AreEqual(typeof(UdpSegmentEmitter).FullName, recorder.Emitter.GetType().FullName);
        }
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void TestSetNullEmitter()
        {
            _ = new AWSXRayRecorderBuilder().WithContextMissingStrategy(ContextMissingStrategy.LOG_ERROR).WithSegmentEmitter(null).Build();
            Assert.Fail();
        }
        [TestMethod]
        public void TestSetTraceContext()
        {
            var recorder = new AWSXRayRecorderBuilder().WithTraceContext(new DummyTraceContext()).Build(); // set custom trace context
            Assert.AreEqual(typeof(DummyTraceContext).FullName, recorder.TraceContext.GetType().FullName);
        }
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void TestSetNullTraceContext()
        {
            _ = new AWSXRayRecorderBuilder().WithTraceContext(null).Build();
            Assert.Fail();
        }
        [TestMethod]
        public void TestExceptionStrategy1() // valid input
        {
            var exceptionStrategy = new DefaultExceptionSerializationStrategy(10);
            var recorder = new AWSXRayRecorderBuilder().WithExceptionSerializationStrategy(exceptionStrategy).Build(); // set custom stackframe size
            AWSXRayRecorder.InitializeInstance(recorder: recorder);
            Assert.AreSame(exceptionStrategy, AWSXRayRecorder.Instance.ExceptionSerializationStrategy);
        }
        [TestMethod]
        public void TestExceptionStrategy2() // invalid input
        {
            var exceptionStrategy = new DefaultExceptionSerializationStrategy(-10);
            var recorder = new AWSXRayRecorderBuilder().WithExceptionSerializationStrategy(exceptionStrategy).Build(); // set custom stackframe size
            AWSXRayRecorder.InitializeInstance(recorder: recorder);
            DefaultExceptionSerializationStrategy actual = AWSXRayRecorder.Instance.ExceptionSerializationStrategy as DefaultExceptionSerializationStrategy;
            Assert.AreEqual(DefaultExceptionSerializationStrategy.DefaultStackFrameSize, actual.MaxStackFrameSize);
        }
        [TestMethod]
        public void TestExceptionStrategy3() // Test default recorder instance
        {
            DefaultExceptionSerializationStrategy actual = AWSXRayRecorder.Instance.ExceptionSerializationStrategy as DefaultExceptionSerializationStrategy;
            Assert.AreEqual(DefaultExceptionSerializationStrategy.DefaultStackFrameSize, actual.MaxStackFrameSize);
        }
        [TestMethod]
        public void TestExceptionStrategy4() // Test custom exception strategy
        {
            var exceptionStrategy = new DummyExceptionSerializationStrategy();
            var recorder = new AWSXRayRecorderBuilder().WithExceptionSerializationStrategy(exceptionStrategy).Build(); // set custom stackframe size
            DummyExceptionSerializationStrategy actual = recorder.ExceptionSerializationStrategy as DummyExceptionSerializationStrategy;
            Assert.AreSame(exceptionStrategy, actual);
        }
        [TestMethod]
        public void TestExceptionStrategy5() // Test custom exception strategy
        {
            List l = new List();
            l.Add(typeof(ArgumentNullException));
            var recorder = new AWSXRayRecorderBuilder().WithExceptionSerializationStrategy(new DefaultExceptionSerializationStrategy(10,l)).Build(); // set custom stackframe size
            AWSXRayRecorder.InitializeInstance(recorder: recorder);
            AWSXRayRecorder.Instance.BeginSegment("parent", TraceId);
            var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity();
            try
            {
                recorder.BeginSubsegment("child1");
                try
                {
                    try
                    {
                        recorder.BeginSubsegment("child2");
                        throw new AmazonServiceException();
                    }
                    catch (AmazonServiceException e)
                    {
                        recorder.AddException(e);
                        recorder.EndSubsegment();
                        throw new ArgumentNullException("value");
                    }
                }
                catch (ArgumentNullException e)
                {
                    recorder.AddException(e);
                    recorder.EndSubsegment();
                    throw new EntityNotAvailableException("Dummy message", e);
                }
            }
            catch (EntityNotAvailableException e)
            {
                recorder.AddException(e);
                recorder.EndSegment();
            }
            Assert.AreEqual("Dummy message", segment.Cause.ExceptionDescriptors[0].Message);
            Assert.AreEqual("EntityNotAvailableException", segment.Cause.ExceptionDescriptors[0].Type);
            Assert.IsFalse(segment.Cause.ExceptionDescriptors[0].Remote); // default false
            Assert.AreEqual(segment.Cause.ExceptionDescriptors[0].Cause, segment.Subsegments[0].Cause.ExceptionDescriptors[0].Id);
            Assert.AreEqual(1, segment.Cause.ExceptionDescriptors.Count);
            Assert.AreEqual("ArgumentNullException", segment.Subsegments[0].Cause.ExceptionDescriptors[0].Type);
            Assert.IsTrue(segment.Subsegments[0].Cause.ExceptionDescriptors[0].Remote); // set to true
            Assert.AreEqual("AmazonServiceException", segment.Subsegments[0].Subsegments[0].Cause.ExceptionDescriptors[0].Type);
            Assert.IsTrue(segment.Subsegments[0].Subsegments[0].Cause.ExceptionDescriptors[0].Remote); // set to true
        }
        [TestMethod]
        public void TestExceptionStrategy6() // Setting stack frame size to 0, so no stack trace is recorded
        {
            int stackFrameSize = 0;
            var recorder = new AWSXRayRecorderBuilder().WithExceptionSerializationStrategy(new DefaultExceptionSerializationStrategy(stackFrameSize)).Build(); // set custom stackframe size
            AWSXRayRecorder.InitializeInstance(recorder: recorder);
            AWSXRayRecorder.Instance.BeginSegment("parent", TraceId);
            var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity();
            try
            {
                recorder.BeginSubsegment("child1");
                try
                {
                    try
                    {
                        recorder.BeginSubsegment("child2");
                        throw new AmazonServiceException();
                    }
                    catch (AmazonServiceException e)
                    {
                        recorder.AddException(e);
                        recorder.EndSubsegment();
                        throw new ArgumentNullException("value");
                    }
                }
                catch (ArgumentNullException e)
                {
                    recorder.AddException(e);
                    recorder.EndSubsegment();
                    throw new EntityNotAvailableException("Dummy message", e);
                }
            }
            catch (EntityNotAvailableException e)
            {
                recorder.AddException(e);
                recorder.EndSegment();
            }
            Assert.AreEqual("Dummy message", segment.Cause.ExceptionDescriptors[0].Message);
            Assert.AreEqual("EntityNotAvailableException", segment.Cause.ExceptionDescriptors[0].Type);
            Assert.IsFalse(segment.Cause.ExceptionDescriptors[0].Remote); // default false
            Assert.AreEqual(segment.Cause.ExceptionDescriptors[0].Cause, segment.Subsegments[0].Cause.ExceptionDescriptors[0].Id);
            Assert.AreEqual(segment.Cause.ExceptionDescriptors[0].Stack.Length, stackFrameSize); // no stack frames should be recorded
            Assert.IsTrue(segment.Cause.ExceptionDescriptors[0].Truncated > 0);
            Assert.AreEqual(1, segment.Cause.ExceptionDescriptors.Count);
            Assert.AreEqual("ArgumentNullException", segment.Subsegments[0].Cause.ExceptionDescriptors[0].Type);
            Assert.AreEqual("AmazonServiceException", segment.Subsegments[0].Subsegments[0].Cause.ExceptionDescriptors[0].Type);
        }
        [TestMethod]
        public void TestExceptionStrategy7() // Test adding the same exception in different subsegments
        {
            List l = new List();
            l.Add(typeof(ArgumentNullException));
            var recorder = new AWSXRayRecorderBuilder().WithExceptionSerializationStrategy(new DefaultExceptionSerializationStrategy(10, l)).Build(); // set custom stackframe size
            AWSXRayRecorder.InitializeInstance(recorder: recorder);
            AWSXRayRecorder.Instance.BeginSegment("parent", TraceId);
            var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity();
            try
            {
                recorder.BeginSubsegment("child1");
                try
                {
                    try
                    {
                        recorder.BeginSubsegment("child2");
                        throw new AmazonServiceException();
                    }
                    catch (AmazonServiceException e)
                    {
                        recorder.AddException(e);
                        recorder.EndSubsegment();
                        throw;
                    }
                }
                catch (AmazonServiceException e)
                {
                    recorder.AddException(e);
                    recorder.EndSubsegment();
                    throw new EntityNotAvailableException("Dummy message", e);
                }
            }
            catch (EntityNotAvailableException e)
            {
                recorder.AddException(e);
                recorder.EndSegment();
            }
            Assert.AreEqual("Dummy message", segment.Cause.ExceptionDescriptors[0].Message);
            Assert.AreEqual("EntityNotAvailableException", segment.Cause.ExceptionDescriptors[0].Type);
            Assert.IsFalse(segment.Cause.ExceptionDescriptors[0].Remote); // default false
            Assert.AreEqual(segment.Cause.ExceptionDescriptors[0].Cause, segment.Subsegments[0].Cause.ExceptionDescriptors[0].Id);
            Assert.AreEqual(1, segment.Cause.ExceptionDescriptors.Count);
            Assert.IsNull(segment.Subsegments[0].Cause.ExceptionDescriptors[0].Type);
            Assert.IsFalse(segment.Subsegments[0].Cause.ExceptionDescriptors[0].Remote); // default false
            Assert.AreEqual("AmazonServiceException", segment.Subsegments[0].Subsegments[0].Cause.ExceptionDescriptors[0].Type);
            Assert.IsTrue(segment.Subsegments[0].Subsegments[0].Cause.ExceptionDescriptors[0].Remote); // set to true
        }
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void TestSetNullExceptionSerializationStrategy()
        {
            _ = new AWSXRayRecorderBuilder().WithExceptionSerializationStrategy(null).Build();
            Assert.Fail();
        }
        private class DummySamplingStrategy : ISamplingStrategy
        {
            public SamplingResponse ShouldTrace(SamplingInput input)
            {
                throw new NotImplementedException();
            }
        }
        private class DummyStreamingStrategy : IStreamingStrategy
        {
            public bool ShouldStream(Entity entity)
            {
                throw new NotImplementedException();
            }
            public void Stream(Entity entity, ISegmentEmitter emitter)
            {
                throw new NotImplementedException();
            }
        }
        private class DummyPlugin : IPlugin
        {
            public string Origin
            {
                get
                {
                    return "Origin";
                }
            }
            public string ServiceName
            {
                get
                {
                    return "DummyService";
                }
            }
            public bool TryGetRuntimeContext(out IDictionary context)
            {
                context = new Dictionary();
                context.Add("key1", "value1");
                return true;
            }
        }
        private class DummyEmitter : ISegmentEmitter
        {
            public void Dispose()
            {
            }
            public void Send(Entity segment)
            {
            }
            public void SetDaemonAddress(string daemonAddress)
            {
            }
        }
        public class DummyTraceContext : ITraceContext
        {
            public void ClearEntity()
            {
            }
            public Entity GetEntity()
            {
                throw new NotImplementedException();
            }
            public void HandleEntityMissing(IAWSXRayRecorder recorder, Exception e, string message)
            {
                throw new NotImplementedException();
            }
            public bool IsEntityPresent()
            {
                throw new NotImplementedException();
            }
            public void SetEntity(Entity entity)
            {
                throw new NotImplementedException();
            }
        }
        private class DummyExceptionSerializationStrategy : ExceptionSerializationStrategy
        {
            public List DescribeException(Exception e, IEnumerable subsegments)
            {
                throw new NotImplementedException();
            }
        }
    }
}