using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using Quadarax.Foundation.Core.Data.Interface.Entity;
using Quadarax.Foundation.Core.Json;
using Quadarax.Foundation.Core.QConsole;
using Quadarax.Foundation.Core.QConsole.Argument;
using Quadarax.Foundation.Core.QConsole.Value;
using Quadarax.Foundation.Core.Value;

namespace BO.Console.Commands.Base
{
    internal abstract class AbstractPostCommand<TData,TIterationResult> : BaseCommand 
        where TData : IModifyDto
        where TIterationResult : IReadDto, new()
    {
          #region *** Constants ***

          private const string CS_ARG_NAME_IN = "in";
          private const string CS_ARG_HINT_IN = "<input_json_file>";
          private const string CS_ARG_DESC_IN = "Input .json file for entity {0}";

          private const string CS_ARG_NAME_LIST = "lst";
          private const string CS_ARG_HINT_LIST = "<show_list>";
          private const string CS_ARG_DESC_LIST = "Shows parsed deserialized input list";

        #endregion

        #region *** Properties ***
        protected IList<TData> Data { get; private set; }
        #endregion

        #region *** Constructors ***
        protected AbstractPostCommand(Engine engine) : base(engine)
        {
        }
        #endregion


        #region *** Protected Overrides ***

        protected override void BeginExecute()
        {
            base.BeginExecute();

            if (WasArgumentSpecified(CS_ARG_NAME_LIST))
            {
                WriteInfo("Parsed input data list:");
                if (Data == null)
                {
                    WriteWarning("No data.");
                    return;
                }

                var cnt = 0;
                foreach (var item in Data)
                {
                    WriteCaption($"[{cnt}]");
                    WriteDtoToConsole(item);
                    cnt++;
                }
                WriteCaption(string.Empty);
                WriteCaption($"Total {cnt} items.");

            }
        }

        protected override Result OnExecute()
        {
            if (Data == null)
                return new Result(new Exception("Nothing to do."));
            if (Data.Count == 0)
                return new Result(new Exception("Nothing to do."));

            var tasks = new List<Task<TIterationResult>>();
            var errors = new List<Exception>();

            foreach (var data in Data)
            {
                var task = new Task<TIterationResult>(() =>
                {
                    try
                    {
                        return IterationExecute(data);
                    }
                    catch (Exception e)
                    {
                        if (e is AggregateException exception)
                            errors.AddRange(exception.InnerExceptions);
                        else
                            errors.Add(e);
                        WriteError(e); 
                        return new TIterationResult();
                    }
                });
                task.Start();
                tasks.Add(task);
                
            }
            Task.WaitAll(tasks.ToArray());
            
            return errors.Count == 0 ? new Result() : new Result(new Exception("Operation failed!"));
        }

        protected abstract TIterationResult IterationExecute(TData data);
            
        protected override void OnValidateArguments()
        {
            base.OnValidateArguments();

            if (!FileSystem.File.Exists(GetArgumentValueOrDefault<string>(CS_ARG_NAME_IN)))
                throw new FileNotFoundException($"Cannot find input file '{GetArgumentValueOrDefault<string>(CS_ARG_NAME_IN)}'");


            var binder = new Binder(FileSystem);
            Data = binder.Load<List<TData>>(GetArgumentValueOrDefault<string>(CS_ARG_NAME_IN));

        }

        protected override IEnumerable<AbstractArgument> OnSetupArguments()
        {
            var args = base.OnSetupArguments().ToList();
            args.Add(new NamedArgument(CS_ARG_NAME_IN , string.Format(CS_ARG_DESC_IN, typeof(TData).Name), CS_ARG_HINT_IN, TypeValuesEnum.String, string.Empty,true));
            args.Add(new FlagArgument(CS_ARG_NAME_LIST , CS_ARG_DESC_LIST, CS_ARG_HINT_LIST,false));
            return args;
        }

        #endregion


    }
}
