using e_suite.Database.Core.Models;
using e_suite.Database.Core.Tables.Activity;
using e_suite.Workflow.Core.Attributes;
using e_suite.Workflow.Core.Interfaces;
using eSuite.Core.Clock;
using eSuite.Core.Enums;
using System.Collections;
using System.Reflection;
using System.Text.Json;

namespace e_suite.Workflow.Core.Extensions;

public static class TaskExtensions
{
    extension(ITask task)
    {
        private Dictionary<string, object?> ToConfigDictionary()
        {
            var dictionary = new Dictionary<string, object?>();

            var type = task.GetType();
            var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (var prop in properties)
            {
                if (!prop.CanRead)
                    continue;

                if (!prop.CanWrite)
                    continue;

                var value = prop.GetValue(task);

                dictionary[prop.Name] = value;
            }

            return dictionary;
        }
    }

    public static ITask ToTask(this TaskDefinition definition, JsonSerializerOptions jsonSerializerOptions)
    {
        var type = Type.GetType(definition.Type);

        if (type == null)
            throw new InvalidOperationException($"Unknown task type '{definition.Type}'.");

        var instance = Activator.CreateInstance(type) as ITask;

        if (instance == null)
            throw new InvalidOperationException($"Type '{definition.Type}' does not implement ITask.");

        FromConfigDictionary(instance, definition.Config, jsonSerializerOptions);

        return instance;
    }

    public static void FromConfigDictionary(this object obj, Dictionary<string, object?> dict, JsonSerializerOptions jsonSerializerOptions)
    {
        var type = obj.GetType();

        foreach (var kvp in dict)
        {
            var prop = type.GetProperty(
                kvp.Key,
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase
            );

            if (prop == null || !prop.CanWrite)
                continue;

            var targetType = prop.PropertyType;
            var value = kvp.Value;

            // If the value is a JsonElement, convert it to the target type
            if (value is JsonElement je)
            {
                if (targetType.IsEnum)
                {
                    // Enums come through as strings
                    value = Enum.Parse(targetType, je.GetString()!, ignoreCase: true);
                }
                else
                {
                    // Deserialize JSON into the target type (handles lists, objects, primitives)
                    value = JsonSerializer.Deserialize(je.GetRawText(), targetType, jsonSerializerOptions);
                }
            }

            prop.SetValue(obj, value);
        }
    }
    
    private static Dictionary<string, object> ExtractConfig(object task)
    {
        var dict = new Dictionary<string, object>();

        foreach (var prop in task.GetType().GetProperties())
        {
            if (!prop.CanRead) continue;
            if (prop.GetIndexParameters().Length > 0) continue; // skip indexers
            if (prop.IsDefined(typeof(RuntimeOnlyAttribute), inherit: true))
                continue;

            var value = prop.GetValue(task);
            if (value != null)
                dict[prop.Name] = value;
        }

        return dict;
    }
    
    extension(ITask task)
    {
        public TaskDefinition ToDefinition()
        {
            if (task is null)
                throw new NullReferenceException();


            return new TaskDefinition
            {
                Type = task.GetType().FullName!,
                Config = task.ToConfigDictionary()!
            };
        }

        private (Type outcomeType, IEnumerable outcomeList)? GetOutcomeList()
        {
            var outcomeInterface = task.GetType()
                .GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType &&
                                     i.GetGenericTypeDefinition() == typeof(IOutcome<>));

            if (outcomeInterface == null)
                return null;

            var outcomeType = outcomeInterface.GetGenericArguments()[0];
            var prop = outcomeInterface.GetProperty("OutcomeActions");

            var list = (IEnumerable)prop!.GetValue(task)!;

            return (outcomeType, list);
        }

        public IEnumerable<Guid> GetTargetGuids()
        {
            var info = task.GetOutcomeList();
            if (info == null)
                yield break;

            foreach (var item in info.Value.outcomeList.Cast<IOutcomeAction>())
                yield return item.Task;
        }

        public IEnumerable<Guid> GetTargetGuids(IEnumerable<string> outcomes)
        {
            var info = task.GetOutcomeList();
            if (info == null)
                return Enumerable.Empty<Guid>();

            var (outcomeType, list) = info.Value;

            var typedOutcomes = outcomes.Select(o =>
                outcomeType.IsEnum
                    ? Enum.Parse(outcomeType, o, true)
                    : Convert.ChangeType(o, outcomeType)
            ).ToHashSet();

            return list
                .Cast<object>()
                .Where(item =>
                {
                    var outcomeProp = item.GetType().GetProperty("Outcome");
                    var outcomeValue = outcomeProp!.GetValue(item);
                    return typedOutcomes.Contains(outcomeValue);
                })
                .Select(item => ((IOutcomeAction)item).Task);
        }

        public async Task StartTask(ActivityTask activityTask, IClock clock)
        {
            var now = clock.GetNow;
            activityTask.StartDateTime = now;
            activityTask.SetState(ActivityState.Active);

            foreach (var assignment in activityTask.Assignments)
            {
                assignment.StartDateTime = now;
                assignment.SetState(ActivityState.Active);
            }

            await task.OnStartedAsync(activityTask);
        }

        public async Task<bool> ProgressTask(ActivityTask activityTask, ICollection<ActivityTask> tasks, IStage stage, IClock clock)
        {
            var hasCompletableTask = false;
            if (activityTask.ActivityState == ActivityState.ReadyToComplete)
            {
                activityTask.FinishDateTime = clock.GetNow;
                activityTask.SetState(ActivityState.Completed);

                await task.OnCompleteAsync(activityTask);
                
                var targetGuids = task.GetTargetGuids(activityTask.Outcomes).ToList();

                foreach (var t in tasks)
                {
                    if (targetGuids.Contains(t.TaskGuid))
                    {
                        var taskDefinition = stage.FindTask(t.TaskGuid)!;
                        await taskDefinition.StartTask(t, clock);
                        if (t.ActivityState == ActivityState.ReadyToComplete)
                        {
                            hasCompletableTask = true;
                        }
                    }
                }
            }

            return hasCompletableTask;
        }

    }


    //public static ITask ToTask(this TaskDefinition def)
    //{
    //    var type = Type.GetType(def.Type, throwOnError: true)!;
    //    var task = (ITask)Activator.CreateInstance(type)!;

    //    if (def.Config != null)
    //        foreach (var kvp in def.Config)
    //        {
    //            var prop = type.GetProperty(kvp.Key);
    //            if (prop == null || !prop.CanWrite) continue;
    //            if (prop.IsDefined(typeof(RuntimeOnlyAttribute), inherit: true))
    //                continue;

    //            object? converted = ConvertValue(kvp.Value, prop.PropertyType);
    //            prop.SetValue(task, converted);
    //        }

    //    if (task is ITemplateValidatable v)
    //    {
    //        var errors = v.ValidateForTemplate().ToList();
    //        if (errors.Count != 0)
    //            throw new InvalidOperationException(
    //                $"Task {task.GetType().Name} is invalid: {string.Join("; ", errors)}");
    //    }

    //    return task;
    //}

    //private static object? ConvertValue(object? value, Type targetType)
    //{
    //    if (value == null) return null;

    //    // Handle enums
    //    if (targetType.IsEnum)
    //        return Enum.Parse(targetType, value.ToString()!);

    //    // Handle Guid
    //    if (targetType == typeof(Guid))
    //        return Guid.Parse(value.ToString()!);

    //    // Handle nullable types
    //    var underlying = Nullable.GetUnderlyingType(targetType);
    //    if (underlying != null)
    //        return Convert.ChangeType(value, underlying);

    //    return Convert.ChangeType(value, targetType);
    //}


    //Todo can only be on a run-time instance
    //public static bool IsCompleted(this ITask task)
    //{
    //    return task.TaskState.In(TaskState.Completed, TaskState.Cancelled);
    //}

    //public static bool ReadyToActivate(this ITask task)
    //{
    //    foreach (var predecessor in task.Predecessors)
    //    {
    //        if (!predecessor.IsCompleted())
    //        {
    //            return false;
    //        }
    //    }

    //    return true;
    //}
}