//===============================================================================
// Microsoft patterns & practices Enterprise Library
// Exception Handling Application Block
//===============================================================================
// Copyright © Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.Collections.Specialized;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Unity;
using Microsoft.Practices.EnterpriseLibrary.Common.Instrumentation.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.TestSupport.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.TestSupport.Instrumentation;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Instrumentation;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Tests.Properties;
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Tests.Configuration.Unity
{
    [TestClass]
    public class ExceptionHandlingBlockExtensionFixture
    {
        private ExceptionHandlingSettings settings;
        private DictionaryConfigurationSource configurationSource;

        [TestInitialize]
        public void SetUp()
        {
            settings = new ExceptionHandlingSettings();
            configurationSource = new DictionaryConfigurationSource();
            configurationSource.Add(ExceptionHandlingSettings.SectionName, settings);
            configurationSource.Add(
                InstrumentationConfigurationSection.SectionName,
                new InstrumentationConfigurationSection(false, false));
        }

        [TestMethod]
        public void CanCreateEmptyPolicy()
        {
            ExceptionPolicyData exceptionPolicyData = new ExceptionPolicyData("policy");
            settings.ExceptionPolicies.Add(exceptionPolicyData);

            using(var container = new UnityContainer().AddExtension(new EnterpriseLibraryCoreExtension(configurationSource)))
            {
                ExceptionPolicyImpl policy = container.Resolve<ExceptionPolicyImpl>("policy");
                Assert.IsNotNull(policy);
            }
        }

        [TestMethod]
        public void CanCreatePolicyWithEmptyTypes()
        {
            ExceptionPolicyData exceptionPolicyData = new ExceptionPolicyData("policy");
            settings.ExceptionPolicies.Add(exceptionPolicyData);

            ExceptionTypeData exceptionTypeData1
                = new ExceptionTypeData("type1", typeof(Exception), PostHandlingAction.None);
            exceptionPolicyData.ExceptionTypes.Add(exceptionTypeData1);
            ExceptionTypeData exceptionTypeData2
                = new ExceptionTypeData("type2", typeof(ArgumentException), PostHandlingAction.NotifyRethrow);
            exceptionPolicyData.ExceptionTypes.Add(exceptionTypeData2);

            using(var container = new UnityContainer().AddExtension(new EnterpriseLibraryCoreExtension(configurationSource)))
            {
                ExceptionPolicyImpl policy = container.Resolve<ExceptionPolicyImpl>("policy");

                Assert.IsNotNull(policy);
                Assert.IsNotNull(policy.GetPolicyEntry(typeof(Exception)));
                Assert.IsNotNull(policy.GetPolicyEntry(typeof(ArgumentException)));
                Assert.IsNull(policy.GetPolicyEntry(typeof(InvalidOperationException)));

                // little detail is exposed for policy entries - need to probe its behavior for a proper assert
                Assert.IsFalse(policy.GetPolicyEntry(typeof(Exception)).Handle(new Exception()));
                Assert.IsTrue(policy.GetPolicyEntry(typeof(ArgumentException)).Handle(new ArgumentException()));
                
            }
        }

        [TestMethod]
        public void PoliciesForExceptionPoliciesCanHandleRepeatedTypes()
        {
            ExceptionPolicyData exceptionPolicy1Data = new ExceptionPolicyData("policy1");
            settings.ExceptionPolicies.Add(exceptionPolicy1Data);

            ExceptionTypeData exceptionTypeData11
                = new ExceptionTypeData("type1", typeof(Exception), PostHandlingAction.None);
            exceptionPolicy1Data.ExceptionTypes.Add(exceptionTypeData11);
            ExceptionTypeData exceptionTypeData12
                = new ExceptionTypeData("type2", typeof(ArgumentException), PostHandlingAction.NotifyRethrow);
            exceptionPolicy1Data.ExceptionTypes.Add(exceptionTypeData12);

            ExceptionPolicyData exceptionPolicy2Data = new ExceptionPolicyData("policy2");
            settings.ExceptionPolicies.Add(exceptionPolicy2Data);

            ExceptionTypeData exceptionTypeData21
                = new ExceptionTypeData("type1", typeof(InvalidOperationException), PostHandlingAction.NotifyRethrow);
            exceptionPolicy2Data.ExceptionTypes.Add(exceptionTypeData21);
            ExceptionTypeData exceptionTypeData22
                = new ExceptionTypeData("type2", typeof(ArgumentNullException), PostHandlingAction.NotifyRethrow);
            exceptionPolicy2Data.ExceptionTypes.Add(exceptionTypeData22);

            using(var container = new UnityContainer().AddExtension(new EnterpriseLibraryCoreExtension(configurationSource)))
            {
                ExceptionPolicyImpl policy = container.Resolve<ExceptionPolicyImpl>("policy1");

                Assert.IsNotNull(policy);
                Assert.IsNotNull(policy.GetPolicyEntry(typeof(Exception)));
                Assert.IsNotNull(policy.GetPolicyEntry(typeof(ArgumentException)));
                Assert.IsNull(policy.GetPolicyEntry(typeof(InvalidOperationException)));

                // this breaks if the type1 for policy2 overrides the policy for policy1's type
                Assert.IsFalse(policy.GetPolicyEntry(typeof(Exception)).Handle(new Exception()));
                Assert.IsTrue(policy.GetPolicyEntry(typeof(ArgumentException)).Handle(new ArgumentException()));
            }
        }

        [TestMethod]
        public void CanCreatePolicyWithSimpleExceptionHandler()
        {
            ExceptionPolicyData exceptionPolicyData = new ExceptionPolicyData("policy");
            settings.ExceptionPolicies.Add(exceptionPolicyData);

            ExceptionTypeData exceptionTypeData = new ExceptionTypeData("type1", typeof(Exception), PostHandlingAction.ThrowNewException);
            exceptionPolicyData.ExceptionTypes.Add(exceptionTypeData);

            ExceptionHandlerData exceptionHandlerData = new WrapHandlerData("handler1", "message",
                typeof(ArgumentException).AssemblyQualifiedName);
            exceptionTypeData.ExceptionHandlers.Add(exceptionHandlerData);

            using(var container = new UnityContainer().AddExtension(new EnterpriseLibraryCoreExtension(configurationSource)))
            {
                ExceptionPolicyImpl policy = container.Resolve<ExceptionPolicyImpl>("policy");

                try
                {
                    policy.HandleException(new Exception("to be wrapped"));
                    Assert.Fail("a new exception should have been thrown");
                }
                catch (ArgumentException e)
                {
                    Assert.AreEqual("message", e.Message);
                }
                
            }
        }

        [TestMethod]
        public void WrapperHandlerCanGetExceptionMessageFromResource()
        {
            const string resourceName = "ExceptionMessage";

            ExceptionPolicyData exceptionPolicyData = new ExceptionPolicyData("policy");
            settings.ExceptionPolicies.Add(exceptionPolicyData);

            ExceptionTypeData exceptionTypeData = new ExceptionTypeData("type1", typeof(Exception), PostHandlingAction.ThrowNewException);
            exceptionPolicyData.ExceptionTypes.Add(exceptionTypeData);

            WrapHandlerData exceptionHandlerData = new WrapHandlerData("handler1", "message",
                typeof(ArgumentException).AssemblyQualifiedName);

            exceptionHandlerData.ExceptionMessageResourceName = resourceName;
            exceptionHandlerData.ExceptionMessageResourceType = typeof(Resources).AssemblyQualifiedName;

            string resourceValue = Resources.ExceptionMessage;

            exceptionTypeData.ExceptionHandlers.Add(exceptionHandlerData);

            using(var container = new UnityContainer().AddExtension(new EnterpriseLibraryCoreExtension(configurationSource)))
            {
                ExceptionPolicyImpl policy = container.Resolve<ExceptionPolicyImpl>("policy");

                try
                {
                    policy.HandleException(new Exception("to be wrapped"));
                    Assert.Fail("a new exception should have been thrown");
                }
                catch (ArgumentException e)
                {
                    Assert.AreEqual(resourceValue, e.Message);
                }
                
            }
        }

        [TestMethod]
        public void PoliciesForExceptionPoliciesCanHandleRepeatedTypesWithRepeatedHandlers()
        {
            ExceptionPolicyData exceptionPolicy1Data = new ExceptionPolicyData("policy1");
            settings.ExceptionPolicies.Add(exceptionPolicy1Data);

            ExceptionTypeData exceptionTypeData11
                = new ExceptionTypeData("type1", typeof(Exception), PostHandlingAction.ThrowNewException);
            exceptionPolicy1Data.ExceptionTypes.Add(exceptionTypeData11);
            ExceptionHandlerData exceptionHandlerData11 = new WrapHandlerData("handler1", "message1",
                typeof(ArgumentException).AssemblyQualifiedName);
            exceptionTypeData11.ExceptionHandlers.Add(exceptionHandlerData11);

            ExceptionPolicyData exceptionPolicy2Data = new ExceptionPolicyData("policy2");
            settings.ExceptionPolicies.Add(exceptionPolicy2Data);

            ExceptionTypeData exceptionTypeData21
                = new ExceptionTypeData("type1", typeof(Exception), PostHandlingAction.ThrowNewException);
            exceptionPolicy2Data.ExceptionTypes.Add(exceptionTypeData21);
            ExceptionHandlerData exceptionHandlerData21 = new WrapHandlerData("handler1", "message2",
                typeof(ArgumentException).AssemblyQualifiedName);
            exceptionTypeData21.ExceptionHandlers.Add(exceptionHandlerData21);

            using(var container = new UnityContainer().AddExtension(new EnterpriseLibraryCoreExtension(configurationSource)))
            {
                ExceptionPolicyImpl policy = container.Resolve<ExceptionPolicyImpl>("policy1");

                try
                {
                    policy.HandleException(new Exception("to be wrapped"));
                    Assert.Fail("a new exception should have been thrown");
                }
                catch (ArgumentException e)
                {
                    Assert.AreEqual("message1", e.Message, "Policy 1 is using the handler definitions from policy 2");
                }
                
            }
        }

        [TestMethod]
        public void PoliciesForExceptionManagerAreCreated()
        {
            ExceptionPolicyData exceptionPolicy1Data = new ExceptionPolicyData("policy1");
            settings.ExceptionPolicies.Add(exceptionPolicy1Data);

            ExceptionTypeData exceptionTypeData11
                = new ExceptionTypeData("type1", typeof(Exception), PostHandlingAction.ThrowNewException);
            exceptionPolicy1Data.ExceptionTypes.Add(exceptionTypeData11);
            CustomHandlerData exceptionHandlerData11 = new CustomHandlerData("handler1", typeof(TestCustomExceptionHandler));
            exceptionHandlerData11.Attributes.Add(TestCustomExceptionHandler.AttributeKey, "handler1");
            exceptionTypeData11.ExceptionHandlers.Add(exceptionHandlerData11);

            using(var container = new UnityContainer().AddExtension(new EnterpriseLibraryCoreExtension(configurationSource)))
            {
                ExceptionManager manager = container.Resolve<ExceptionManager>();
                Assert.IsNotNull(manager);

                Exception exceptionToThrow = new Exception("some message");

                try
                {
                    manager.Process(() => { throw exceptionToThrow; }, "policy1");
                    Assert.Fail("a new exception should have been thrown");
                }
                catch (Exception e)
                {
                    Assert.AreSame(exceptionToThrow, e.InnerException);
                    Assert.AreEqual("handler1", e.Message);
                }
            }
        }

        [TestMethod]
        [Ignore]    // TODO use a different instrumentation mechanism to test
        public void ExceptionManagerGetsInstrumented()
        {
            //using(var container = new UnityContainer().AddExtension(new EnterpriseLibraryCoreExtension(configurationSource)))
            //{
            //    ExceptionManager manager = container.Resolve<ExceptionManager>();
            //    Assert.IsNotNull(manager);

            //    Exception exceptionToThrow = new Exception("some message");

            //    using (WmiEventWatcher eventListener = new WmiEventWatcher(1))
            //    {
            //        try
            //        {
            //            manager.HandleException(new Exception(), "non-existing policy");
            //        }
            //        catch (ExceptionHandlingException)
            //        {
            //            eventListener.WaitForEvents();
            //            Assert.AreEqual(1, eventListener.EventsReceived.Count);
            //            Assert.AreEqual(typeof(ExceptionHandlingFailureEvent).Name,
            //                            eventListener.EventsReceived[0].ClassPath.ClassName);
            //            Assert.AreEqual("non-existing policy", eventListener.EventsReceived[0].GetPropertyValue("InstanceName"));
            //        }
            //    }
                
            //}
        }


        [TestMethod]
        public void ExceptionManagerLifetimeIsSingleton()
        {
            using(var container = new UnityContainer().AddExtension(new EnterpriseLibraryCoreExtension(configurationSource)))
            {
                ExceptionManager manager = container.Resolve<ExceptionManager>();
                ExceptionManager manager2 = container.Resolve<ExceptionManager>();

                Assert.AreSame(manager, manager2);
            }
        }

        public class TestCustomExceptionHandler : MockCustomProviderBase, IExceptionHandler
        {
            public TestCustomExceptionHandler(NameValueCollection attributes)
                : base(attributes)
            { }

            public Exception HandleException(Exception exception, Guid handlingInstanceId)
            {
                return new Exception(customValue, exception);
            }
        }
    }
}
