using e_suite.Workflow.Core.Attributes;
using e_suite.Workflow.Core.Interfaces;
using e_suite.Workflow.Core.Tasks;
using System.Collections.Concurrent;
using static System.Net.Mime.MediaTypeNames;

namespace e_suite.Workflow.Core.Extensions;

public static class StageExtensions
{
    //Todo can only be on a run-time instance
    //public static bool CanComplete<T>(this IStage<T> stage)
    //    where T : TaskTypeAttribute
    //{
    //    return stage.Tasks.All(x =>
    //        x.IsCompleted());
    //}

    private static readonly Dictionary<string, Type> TaskAttributeMap =
        AppDomain.CurrentDomain
            .GetAssemblies()
            .SelectMany(a =>
            {
                try { return a.GetTypes(); }
                catch { return Array.Empty<Type>(); }
            })
            .Where(t =>
                t.IsClass &&
                !t.IsAbstract &&
                typeof(TaskTypeAttribute).IsAssignableFrom(t))
            .ToDictionary(
                t => t.Name,          // e.g. "GeneralTaskAttribute"
                t => t,
                StringComparer.OrdinalIgnoreCase
            );

    public static Type? GetTaskAttributeType(string taskType)
    {
        if (string.IsNullOrWhiteSpace(taskType))
            return null;

        var key = taskType.Trim();

        // Try exact match first
        if (TaskAttributeMap.TryGetValue(key, out var type))
            return type;

        // Try with "Attribute" suffix added
        if (TaskAttributeMap.TryGetValue(key + "Attribute", out type))
            return type;

        throw new ArgumentOutOfRangeException(nameof(taskType), $"Unknown task type '{taskType}'");
    }


    private static readonly ConcurrentDictionary<string, IEnumerable<Type>> AllowedTasksCache = new();

    [System.Diagnostics.CodeAnalysis.SuppressMessage(
        "Style", "IDE0305:Collection initialization can be simplified",
        Justification = "Materialization required for caching")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
        "Style", "IDE0060:Removed unused parameter 'stage' if it is not part of a shipped public API",
        Justification = "parameter required for extension method definition")]


    public static IEnumerable<Type> GetAllowedTaskTypes(Type? attributeType)
    {
        if (attributeType == null)
        {
            return AllowedTasksCache.GetOrAdd("__NULL__", _ =>
            {
                var generalTasks = GetAllowedTaskTypes(typeof(GeneralTaskAttribute));
                var approvalTasks = GetAllowedTaskTypes(typeof(ApprovalTaskAttribute));

                return generalTasks.Concat(approvalTasks).ToArray();
            });
        }


        return AllowedTasksCache.GetOrAdd(attributeType!.FullName!, _ =>
        {
            return AppDomain.CurrentDomain
                .GetAssemblies()
                .SelectMany(a =>
                {
                    try { return a.GetTypes(); }
                    catch { return []; }
                })
                .Where(t =>
                    t.IsClass &&
                    !t.IsAbstract &&
                    t.GetCustomAttributes(attributeType, inherit: true).Length != 0)
                .ToArray();
        });
    }
    
    extension<T>(IStage<T> stage) where T : TaskTypeAttribute
    {
        public IEnumerable<Type> AllowedTasks()
        {
            var attributeType = typeof(T);
            return GetAllowedTaskTypes(attributeType);
        }
    }

    extension(IStage stage)
    {
        public IEnumerable<Guid> GetAllTargets()
        {
            return stage.Tasks.SelectMany(t => t.GetTargetGuids()).Distinct();
        }

        public IEnumerable<ITask> GetStartTasks()
        {
            var allTargets = stage.GetAllTargets().ToHashSet();
            return stage.Tasks.Where(t => !allTargets.Contains(t.Guid));
        }

        public ITask? FindTask(Guid taskGuid, bool deepScan = true)
        {
            foreach (var task in stage.Tasks)
            {
                if (task.Guid == taskGuid)
                    return task;

                if (!deepScan || task is not IStage nestedStage)
                    continue;

                var found = nestedStage.FindTask(taskGuid, deepScan);
                if (found != null) 
                    return found;
            }

            return null;
        }
    }
}