using MainShell.Common;
using MW.WorkFlow;
using System;
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;

namespace MainShell.Process
{
    public class CompositeActivity : IActivity
    {
        private readonly IEnumerable<IActivity> _subSteps;
        private readonly IEnumerable<IActivity> _finallySteps;

        public string Name { get; }

        public CompositeActivity(string name, IEnumerable<IActivity> subSteps)
            : this(name, subSteps, Array.Empty<IActivity>())
        {
        }

        public CompositeActivity(string name, IEnumerable<IActivity> subSteps, IEnumerable<IActivity> finallySteps)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException("Composite activity name cannot be null or whitespace.", nameof(name));
            }

            Name = name;
            _subSteps = subSteps ?? throw new ArgumentNullException(nameof(subSteps));
            _finallySteps = finallySteps ?? Array.Empty<IActivity>();
        }

        public async Task<ActivityResult> ExecuteAsync(WorkflowContext context, ActivityControl activityControl)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (activityControl == null)
            {
                throw new ArgumentNullException(nameof(activityControl));
            }

            activityControl.ThrowIfCancellationRequested();

            context.TryGetData<string>(WorkflowContextKeys.ParentActivityName, out var previousParent);
            context.SetData(WorkflowContextKeys.ParentActivityName, Name);

            ActivityResult result = ActivityResult.Success;
            Exception mainException = null;
            Exception cleanupException = null;

            try
            {
                foreach (IActivity step in _subSteps)
                {
                    activityControl.ThrowIfCancellationRequested();

                    result = await step.ExecuteAsync(context, activityControl);
                    if (result != ActivityResult.Success)
                    {
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                mainException = ex;
            }
            finally
            {
                foreach (IActivity cleanupStep in _finallySteps)
                {
                    try
                    {
                        ActivityResult cleanupResult = await cleanupStep.ExecuteAsync(context, activityControl);
                        if (mainException == null && result == ActivityResult.Success && cleanupResult != ActivityResult.Success)
                        {
                            result = cleanupResult;
                        }
                    }
                    catch (Exception ex)
                    {
                        if (mainException == null && result == ActivityResult.Success)
                        {
                            cleanupException = ex;
                            break;
                        }
                    }
                }

                context.SetData(WorkflowContextKeys.ParentActivityName, previousParent ?? string.Empty);
            }

            if (mainException != null)
            {
                ExceptionDispatchInfo.Capture(mainException).Throw();
            }

            if (cleanupException != null)
            {
                ExceptionDispatchInfo.Capture(cleanupException).Throw();
            }

            return result;
        }
    }
}