using MainShell.Common;
using MainShell.ProcessResult;
using MW.WorkFlow;
using System;
using System.Threading.Tasks;

namespace MainShell.Process
{
    public abstract class ActivityAbstractBase : IActivity
    {
        public string Name { get; protected set; }

        protected ProcessResultManager ResultManager { get; private set; }

        protected string CurrentWorkflowName { get; private set; } = "UnknownFlow";

        protected string ParentActivityName { get; private set; } = string.Empty;

        protected ActivityAbstractBase(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentException("Activity name cannot be null or whitespace.", nameof(name));
            }

            Name = name;
        }

        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));
            }

            var result = ActivityResult.Failure;
            var hasFaulted = false;

            try
            {
                if (context.TryGetData<string>(WorkflowContextKeys.WorkflowName, out var flowName))
                {
                    CurrentWorkflowName = flowName;
                }

                if (context.TryGetData<string>(WorkflowContextKeys.ParentActivityName, out var parentName))
                {
                    ParentActivityName = parentName;
                }
                else
                {
                    ParentActivityName = string.Empty;
                }

                if (context.TryGetData<ProcessResultManager>(WorkflowContextKeys.ProcessResultManager, out var manager))
                {
                    ResultManager = manager;
                    ResultManager.UpdateFlowState(CurrentWorkflowName, ParentActivityName, Name, ProcessExecutionStatus.Running, null);
                }

                PrepareExecute(context, activityControl);
                result = await OnExecuteAsync(context, activityControl);
                return result;
            }
            catch (OperationCanceledException)
            {
                ResultManager?.UpdateFlowState(CurrentWorkflowName, ParentActivityName, Name, ProcessExecutionStatus.Canceled, null);
                throw;
            }
            catch (Exception ex)
            {
                if (!HasWorkflowFailure(context))
                {
                    SetWorkflowFailure(
                        context,
                        MessageKey.ProcessStepFailedWithReason,
                        new object[] { Name, ex.Message ?? string.Empty });
                }

                var errorMsg = ResolveWorkflowFailureMessage(context);
                ResultManager?.UpdateFlowState(CurrentWorkflowName, ParentActivityName, Name, ProcessExecutionStatus.Faulted, errorMsg);
                hasFaulted = true;
                throw;
            }
            finally
            {
                AfterExecute(context, activityControl);

                if (ResultManager != null && !hasFaulted)
                {
                    if (result == ActivityResult.Success)
                    {
                        ResultManager.UpdateFlowState(CurrentWorkflowName, ParentActivityName, Name, ProcessExecutionStatus.Completed, null);
                    }
                    else if (result == ActivityResult.Canceled)
                    {
                        ResultManager.UpdateFlowState(CurrentWorkflowName, ParentActivityName, Name, ProcessExecutionStatus.Canceled, null);
                    }
                    else
                    {
                        ResultManager.UpdateFlowState(
                            CurrentWorkflowName,
                            ParentActivityName,
                            Name,
                            ProcessExecutionStatus.Faulted,
                            ResolveWorkflowFailureMessage(context));
                    }
                }
            }
        }

        protected ActivityResult Fail(WorkflowContext context, MessageKey failureMessageKey, object[] failureMessageArguments)
        {
            SetWorkflowFailure(context, failureMessageKey, failureMessageArguments);
            return ActivityResult.Failure;
        }

        protected void SetWorkflowFailure(WorkflowContext context, MessageKey failureMessageKey, object[] failureMessageArguments)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var normalized = NormalizeFailureMessageArguments(failureMessageArguments);
            context.SetData(WorkflowContextKeys.WorkflowFailureMessageKey, failureMessageKey);
            context.SetData(WorkflowContextKeys.WorkflowFailureMessageArguments, normalized);
            context.SetData(WorkflowContextKeys.WorkflowFailureMessage, LanguageResourceHelper.Format(failureMessageKey, normalized));
        }

        protected void SetWorkflowFailure(WorkflowContext context, string failureMessage)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            context.SetData(WorkflowContextKeys.WorkflowFailureMessageKey, MessageKey.None);
            context.SetData<object[]>(WorkflowContextKeys.WorkflowFailureMessageArguments, null);
            context.SetData(WorkflowContextKeys.WorkflowFailureMessage, failureMessage ?? string.Empty);
        }

        protected bool HasWorkflowFailure(WorkflowContext context)
        {
            if (context.TryGetData<MessageKey>(WorkflowContextKeys.WorkflowFailureMessageKey, out var failureMessageKey) &&
                failureMessageKey > MessageKey.None)
            {
                return true;
            }

            return context.TryGetData<string>(WorkflowContextKeys.WorkflowFailureMessage, out var failureMessage) &&
                !string.IsNullOrWhiteSpace(failureMessage);
        }

        protected string ResolveWorkflowFailureMessage(WorkflowContext context)
        {
            if (context.TryGetData<MessageKey>(WorkflowContextKeys.WorkflowFailureMessageKey, out var failureMessageKey) &&
                failureMessageKey > MessageKey.None)
            {
                return LanguageResourceHelper.Format(failureMessageKey, GetWorkflowFailureMessageArguments(context));
            }

            if (context.TryGetData<string>(WorkflowContextKeys.WorkflowFailureMessage, out var failureMessage) &&
                !string.IsNullOrWhiteSpace(failureMessage))
            {
                return failureMessage;
            }

            return string.Empty;
        }

        protected object[] GetWorkflowFailureMessageArguments(WorkflowContext context)
        {
            if (context.TryGetData<object[]>(WorkflowContextKeys.WorkflowFailureMessageArguments, out var failureMessageArguments) &&
                failureMessageArguments != null)
            {
                return failureMessageArguments;
            }

            return Array.Empty<object>();
        }

        protected object[] NormalizeFailureMessageArguments(object[] failureMessageArguments)
        {
            return failureMessageArguments ?? Array.Empty<object>();
        }

        protected abstract Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl);

        protected virtual void PrepareExecute(WorkflowContext context, ActivityControl activityControl)
        {
        }

        protected virtual void AfterExecute(WorkflowContext context, ActivityControl activityControl)
        {
        }
    }
}
