using Microsoft.AspNetCore.SignalR;
using Microsoft.CodeAnalysis;
using OpenIddict.Client;
using RobotNet.OpenIddictClient;
using RobotNet.Script.Shares;
using RobotNet.ScriptManager.Hubs;
using RobotNet.ScriptManager.Models;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;

namespace RobotNet.ScriptManager.Services;

public class ScriptGlobalsManager
{
    public IDictionary<string, object?> Globals => _globals;
    private readonly ConcurrentDictionary<string, object?> _globals = new();
    private readonly Dictionary<string, Type> _globalsTypes = [];
    private readonly Dictionary<string, ScriptVariableSyntax> variables = [];
    private readonly IHubContext<ConsoleHub> consoleHubContext;
    private readonly ScriptRobotManager robotManager;
    private readonly ScriptMapManager mapManager;
    private readonly ScriptConnectionManager ConnectionManager;
    private readonly IServiceScopeFactory scopeFactory;

    public ScriptGlobalsManager(IConfiguration configuration, OpenIddictClientService openIddictClient, IHubContext<ConsoleHub> _consoleHubContext, ScriptConnectionManager connectionManager, IServiceScopeFactory _scopeFactory)
    {
        consoleHubContext = _consoleHubContext;
        this.scopeFactory = _scopeFactory;
        ConnectionManager = connectionManager;

        var robotManagerOptions = configuration.GetSection("RobotManager").Get<OpenIddictResourceOptions>() ?? throw new InvalidOperationException("OpenID configuration not found or invalid format.");
        robotManager = new ScriptRobotManager(openIddictClient, robotManagerOptions.Url, robotManagerOptions.Scopes);

        var mapManagerOptions = configuration.GetSection("MapManager").Get<OpenIddictResourceOptions>() ?? throw new InvalidOperationException("OpenID configuration not found or invalid format.");
        mapManager = new ScriptMapManager(openIddictClient, mapManagerOptions.Url, mapManagerOptions.Scopes);
    }

    public void LoadVariables(IEnumerable<ScriptVariableSyntax> variableSynctaxs)
    {
        _globals.Clear();
        _globalsTypes.Clear();
        variables.Clear();
        foreach (var v in variableSynctaxs)
        {
            variables.Add(v.Name, new(v.Name, v.Type, v.DefaultValue, v.PublicRead, v.PublicWrite));
            _globals.TryAdd(v.Name, v.DefaultValue);
            _globalsTypes.TryAdd(v.Name, v.Type);
        }
    }

    public IEnumerable<ScriptVariableDto> GetVariablesData()
    {
        var vars = new List<ScriptVariableDto>();
        foreach (var v in variables.Values)
        {
            if (v.PublicRead)
            {
                vars.Add(new ScriptVariableDto(v.Name, v.TypeName, _globals[v.Name]?.ToString() ?? "null"));
            }
        }

        return vars;
    }

    public Type? GetVariableType(string name)
    {
        if (_globalsTypes.TryGetValue(name, out var type))
        {
            return type;
        }
        
        return null;
    }

    public IDictionary<string, object?> GetRobotNetTask(string name)
    {
        var globalRobotNet = new ScriptGlobalsRobotNetTask(new ScriptTaskLogger(consoleHubContext, name), robotManager, mapManager, ConnectionManager,  scopeFactory);
        
        return ConvertGlobalsToDictionary(globalRobotNet);
    }

    public IDictionary<string, object?> GetRobotNetMission(Guid missionId)
    {
        var globalRobotNet = new ScriptGlobalsRobotNetMission(missionId, new ScriptMissionLogger(consoleHubContext, missionId), robotManager, mapManager, ConnectionManager, scopeFactory);

        return ConvertGlobalsToDictionary(globalRobotNet);
    }

    public void SetValue(string name, string value)
    {
        if (variables.TryGetValue(name, out var variable))
        {
            if (variable.PublicWrite)
            {
                if (_globalsTypes.TryGetValue(name, out var type))
                {
                    try
                    {
                        var convertedValue = Convert.ChangeType(value, type);
                        _globals[name] = convertedValue;
                    }
                    catch (InvalidCastException)
                    {
                        throw new InvalidOperationException($"Cannot convert value '{value}' to type '{type.FullName}' for variable '{name}'.");
                    }
                }
                else
                {
                    throw new KeyNotFoundException($"Variable type for '{name}' not found.");
                }
            }
            else
            {
                throw new InvalidOperationException($"Variable '{name}' is not writable.");
            }
        }
        else
        {
            throw new KeyNotFoundException($"Variable '{name}' not found.");
        }
    }

    public void ResetValue(string name)
    {
        if (variables.TryGetValue(name, out var variable))
        {
            if (_globalsTypes.TryGetValue(name, out var type))
            {
                _globals[name] = Convert.ChangeType(variable.DefaultValue, type);
            }
            else
            {
                throw new KeyNotFoundException($"Variable type for '{name}' not found.");
            }
        }
        else
        {
            throw new KeyNotFoundException($"Variable '{name}' not found.");
        }
    }

    private static Dictionary<string, object?> ConvertGlobalsToDictionary(IRobotNetGlobals globals)
    {
        var robotnet = new Dictionary<string, object?>();
        var type = typeof(IRobotNetGlobals);


        foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
        {
            robotnet.TryAdd(field.Name, field.GetValue(globals));
        }

        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (prop.GetIndexParameters().Length == 0)
            {
                // Forward getter if available
                if (prop.GetGetMethod() != null)
                {
                    var getter = Delegate.CreateDelegate(
                        Expression.GetDelegateType([prop.PropertyType]),
                        globals,
                        prop.GetGetMethod()!
                    );
                    robotnet.TryAdd($"get_{prop.Name}", getter);
                }
                // Forward setter if available
                if (prop.GetSetMethod() != null)
                {
                    var setter = Delegate.CreateDelegate(
                        Expression.GetDelegateType([prop.PropertyType, typeof(void)]),
                        globals,
                        prop.GetSetMethod()!
                    );
                    robotnet.TryAdd($"set_{prop.Name}", setter);
                }
            }
            else
            {
                // Handle indexers (properties with parameters)
                var indexParams = prop.GetIndexParameters().Select(p => p.ParameterType).ToArray();

                // Forward indexer getter if available
                if (prop.GetGetMethod() != null)
                {
                    var getterParamTypes = indexParams.Concat([prop.PropertyType]).ToArray();
                    var getterDelegate = Delegate.CreateDelegate(
                        Expression.GetDelegateType(getterParamTypes),
                        globals,
                        prop.GetGetMethod()!
                    );
                    robotnet.TryAdd($"get_{prop.Name}_indexer", getterDelegate);
                }

                // Forward indexer setter if available
                if (prop.GetSetMethod() != null)
                {
                    var setterParamTypes = indexParams.Concat([prop.PropertyType, typeof(void)]).ToArray();
                    var setterDelegate = Delegate.CreateDelegate(
                        Expression.GetDelegateType(setterParamTypes),
                        globals,
                        prop.GetSetMethod()!
                    );
                    robotnet.TryAdd($"set_{prop.Name}_indexer", setterDelegate);
                }
            }
        }
        foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
        {
            if (!method.IsSpecialName)
            {
                var parameters = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.FullName} {p.Name}"));
                var args = string.Join(", ", method.GetParameters().Select(p => p.Name));
                var returnType = method.ReturnType == typeof(void) ? "void" : method.ReturnType.FullName;

                var paramTypes = string.Join(",", method.GetParameters().Select(p => p.ParameterType.FullName));
                var methodKey = $"{method.Name}({paramTypes})";

                var del = Delegate.CreateDelegate(
                    Expression.GetDelegateType([.. method.GetParameters().Select(p => p.ParameterType), method.ReturnType]),
                    globals,
                    method
                );
                // TODO: tôi muốn thay vì sử dụng tên phương thức, tôi muốn sử dụng tên phương thức với danh sánh kiểu dữ liệu của các tham số trong dấu ngoặc tròn
                robotnet.TryAdd(methodKey, del);
            }
        }
        return robotnet;
    }
}
