using System.Globalization;
using NodePipeline.Abstractions;
using NodePipeline.Abstractions.Exceptions.NodeFactory;
using NodePipeline.Abstractions.Interfaces;
using NodePipeline.Abstractions.Interfaces.Nodes;
using NodePipeline.Configuration.Abstractions.Models.Execute;
using NodePipeline.Engine.Abstractions;
using NodePipeline.Engine.NodeFields;
using NodePipeline.Engine.Utils;
using NodePipeline.Engine.Abstractions.Validation;

// ReSharper disable CheckNamespace
// ReSharper disable ConvertTypeCheckPatternToNullCheck
// ReSharper disable RedundantCast
// ReSharper disable RedundantNameQualifier
// ReSharper disable ReturnTypeCanBeNotNullable

// ReSharper disable UnusedMember.Local
// ReSharper disable UnusedParameter.Local
#nullable enable

namespace NodePipeline.Engine.Generated;

public sealed class GeneratedNodeFactory : INodeFactory
{
    public Dictionary<string, Func<INode>> NodeFactories { get; set; } = new();
    public IPipelineLocalizationProvider PipelineLocalizationProvider { get; set; } = new PipelineLocalizationProvider();

    public INode CreateNode(string pipelineId, NodeConfig config)
    {
        INode? node;
        if (NodeFactories.TryGetValue(config.Type, out var f))
        {
            node = f();
            InitializeNodeFields(pipelineId, node, config.Id);
            return node;
        }

        switch (config.Type)
        {
            default:
                throw new NodeNotFoundException(pipelineId, config.Type, config.Id);
        }
        InitializeNodeFields(pipelineId, node, config.Id);
        return node ?? throw new NodeWasNotInitializedException(pipelineId, config.Type, config.Id);
    }

    private void InitializeNodeFields(string pipelineId, INode node, string nodeId)
    {
        switch (node)
        {
            default:
                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);
        }
    }


    public IEnumerable<string> GetParameterCodes(string nodeType)
    {
        switch (nodeType)
        {
            default:
                throw new NodeTypeNotFoundException(nodeType);
        }
    }


    public object? ReadParameterValue(string pipelineId, string nodeId, string nodeType, string parameterName, string? valueString)
    {
        switch (nodeType)
        {
            default:
                throw new NodeTypeNotFoundException(nodeType);
        }
    }


    public Dictionary<NodePortKey, NodePortInfo> GetAllPorts(string pipelineId, HashSet<string> nodeTypes, Dictionary<string, IEnumerable<string>> nodeList)
    {
        var result = new Dictionary<NodePortKey, NodePortInfo>();
        foreach (var nodeType in nodeTypes)
        {
            if (!nodeList.TryGetValue(nodeType, out var ids)) continue;
            foreach (var nodeId in ids)
            {
                var ports = GetNodePorts(pipelineId, nodeType, nodeId);
                foreach (var kv in ports) result.Add(kv.Key, kv.Value);
            }
        }
        return result;
    }

    public IEnumerable<NodePortInfo> GetNodeInputPorts(string pipelineId, string nodeType, string nodeId)
    {
        var ports = GetNodePorts(pipelineId, nodeType, nodeId);
        foreach (var kv in ports) if (kv.Key.Input) yield return kv.Value;
    }

    private Dictionary<NodePortKey, NodePortInfo> GetNodePorts(string pipelineId, string nodeType, string nodeId)
    {
        switch (nodeType)
        {
            default:
                throw new NodeNotFoundException(pipelineId, nodeType, nodeId);
        }
    }


    private IReadOnlyDictionary<string, object> GetNodeOutputs(string pipelineId, string nodeId, INode node)
    {
        switch (node)
        {
            default:
                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);
        }
    }



    public void ConnectPorts<TNode>(string pipelineId, TNode node, string nodeId, Dictionary<string, InputSource> inputs, IReadOnlyDictionary<string, INode> createdNodes) where TNode : INode
    {
        switch (node)
        {
            default:
                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);
        }
    }

    private PortConnectionInfo? TryConnectPorts<T>(string pipelineId, string nodeId, string nodeType, INodeField<T>? nodeInput, string nodeInputName, Dictionary<string, InputSource> inputs, string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool isRequired = false) where T : notnull
    {
        if (!inputs.TryGetValue(inputKey, out var connection))
            return !isRequired ? null : throw new InputPortHasNoRequiredConnectionException(pipelineId, inputKey, nodeId, nodeType);
        var fromNodeId = connection.NodeId;
        var fromPortName = connection.OutputName;
        if (nodeInput == null) throw new InputPortNotFoundException(pipelineId, inputKey, nodeId, nodeType);
        if (!createdNodes.TryGetValue(fromNodeId, out var fromNode)) throw new OutputPortNodeNotFoundException(pipelineId, fromNodeId, nodeId);
        var outputs = GetNodeOutputs(pipelineId, fromNodeId, fromNode);
        var outputFieldKey = NodeFieldCodeGenerator.GenerateFieldCode(fromNodeId, fromPortName, FieldDirection.Output);
        if (!outputs.TryGetValue(outputFieldKey, out var outputFieldObj)) throw new OutputPortNotFoundException(pipelineId, fromPortName, fromNodeId, nodeId);
        var fromNodeType = GetNodeType(pipelineId, fromNodeId, fromNode);
        if (outputFieldObj is INodeField<T> outStrict && nodeInput is INodeField<T> inStrict)
        {
            PortConnector.ConnectStatic(outStrict, inStrict);
            return new PortConnectionInfo(true, outputFieldObj, fromPortName, fromNodeType, fromNodeId);
        }
        return new PortConnectionInfo(false, outputFieldObj, fromPortName, fromNodeType, fromNodeId);
    }


    private void ConnectPortsStruct<T>(string pipelineId, string nodeId,string nodeType, INodeField<T>? nodeInput, string nodeInputName, Dictionary<string, InputSource> inputs,
        string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool disallowNullableOutput = false, bool isRequired = false)
        where T : struct
    {
        var portConnectionInfo = TryConnectPorts(pipelineId, nodeId, nodeType, nodeInput, nodeInputName, inputs, inputKey, createdNodes, isRequired);
        if (portConnectionInfo is null || portConnectionInfo.Connected) return;
        if (typeof(T).IsValueType && portConnectionInfo.OutputFieldObj is INodeField<T?> outputField)
        {
            PortConnector.ConnectRuntimeFromNullableStruct(outputField, nodeInput!,
                nodeId, inputKey, portConnectionInfo.FromNodeId, portConnectionInfo.FromPortName);
            return;
        }
        if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNullable && nodeInput is INodeField<T?> inNullable)
        {
            PortConnector.ConnectRuntimeBothNullable(outNullable, inNullable);
            return;
        }
        if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNToStrict && nodeInput != null)
        {
            if (disallowNullableOutput)
            {
                throw new NonNullableInputRestrictsConnectionToNullableOutputException(pipelineId, nodeId, nodeType, inputKey, portConnectionInfo.FromPortName, portConnectionInfo.FromNodeId);
            }
            PortConnector.ConnectRuntimeFromNullableStruct(outNToStrict, nodeInput, currentNodeId: nodeId,
                inputPortName: inputKey, sourceNodeId: portConnectionInfo.FromNodeId, outputPortName: portConnectionInfo.FromPortName);
            return;
        }
        if (portConnectionInfo.OutputFieldObj is INodeField<T> outStrictToN && nodeInput is INodeField<T?> inNullable2)
        {
            PortConnector.ConnectRuntimeToNullableStruct(outStrictToN, inNullable2);
            return;
        }
        throw new PortsTypeMismatchException(pipelineId, inputKey, nodeType, nodeId,
            portConnectionInfo.FromPortName, portConnectionInfo.FromNodeType, portConnectionInfo.FromNodeId);
    }

    private void ConnectPortsClass<T>(string pipelineId, string nodeId,string nodeType, INodeField<T>? nodeInput, string nodeInputName, Dictionary<string, InputSource> inputs,
        string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool disallowNullableOutput = false, bool isRequired = false)
        where T : class
    {
        var portConnectionInfo = TryConnectPorts(pipelineId, nodeId, nodeType, nodeInput, nodeInputName, inputs, inputKey, createdNodes, isRequired);
        if (portConnectionInfo is null || portConnectionInfo.Connected) return;
        if (!typeof(T).IsValueType && portConnectionInfo.OutputFieldObj is INodeField<T?> outputField)
        {
            PortConnector.ConnectRuntimeFromNullableClass(outputField, nodeInput!,
                nodeId, inputKey, portConnectionInfo.FromNodeId, portConnectionInfo.FromPortName);
            return;
        }
        if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNullable && nodeInput is INodeField<T?> inNullable)
        {
            PortConnector.ConnectRuntimeBothNullable(outNullable, (INodeField<T?>)inNullable);
            return;
        }
        if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNToStrict && nodeInput != null)
        {
            if (disallowNullableOutput)
            {
                throw new NonNullableInputRestrictsConnectionToNullableOutputException(pipelineId, nodeId, nodeType, inputKey, portConnectionInfo.FromPortName, portConnectionInfo.FromNodeId);
            }
            PortConnector.ConnectRuntimeFromNullableClass(outNToStrict, nodeInput, currentNodeId: nodeId,
                inputPortName: inputKey, sourceNodeId: portConnectionInfo.FromNodeId, outputPortName: portConnectionInfo.FromPortName);
            return;
        }
        if (portConnectionInfo.OutputFieldObj is INodeField<T> outStrictToN && nodeInput is INodeField<T?> inNullable2)
        {
            PortConnector.ConnectRuntimeToNullableClass(outStrictToN, (INodeField<T?>)inNullable2);
            return;
        }
        throw new PortsTypeMismatchException(pipelineId, inputKey, nodeType, nodeId,
            portConnectionInfo.FromPortName, portConnectionInfo.FromNodeType, portConnectionInfo.FromNodeId);
    }

    private static T? ParseParameter<T>(string? valueString)
    {
        var type = typeof(T);
        if (type == typeof(string)) return valueString is not null ? (T)(object)valueString : default;
        if (string.IsNullOrEmpty(valueString)) return default;
        if (type == typeof(int)) return (T)(object)int.Parse(valueString);
        if (type == typeof(double)) return (T)(object)double.Parse(valueString, CultureInfo.InvariantCulture);
        if (type == typeof(bool)) return (T)(object)bool.Parse(valueString);
        throw new NotSupportedException(type.Name);
    }

    public object GetParameterDefaultValue(string nodeType, string parameterName)
    {
        switch (nodeType)
        {
            default:
                throw new NodeParameterNotFoundException(parameterName, nodeType);
        }
    }

    private static T? GetDefaultValue<T>()
    {
        var type = typeof(T);
        if (type == typeof(string)) return (T)(object)string.Empty;
        if (type == typeof(int)) return (T)(object)0;
        if (type == typeof(double)) return (T)(object)0d;
        if (type == typeof(decimal)) return (T)(object)0m;
        if (type == typeof(bool)) return (T)(object)false;
        if (type == typeof(Guid)) return (T)(object)Guid.Empty;
        if (type.IsEnum) return default;
        throw new NotSupportedException(type.Name);
    }

    public void SetNodeParametersValues<TNode>(string pipelineId, TNode node, string nodeId, Dictionary<string, object?> parameters) where TNode : INode
    {
        switch (node)
        {
            default:
                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);
        }
    }


    private static string GetNodeType(string pipelineId, string nodeId, INode node)
    {
        switch (node)
        {
            default:
                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);
        }
    }


    private record PortConnectionInfo(bool Connected, object? OutputFieldObj, string FromPortName, string FromNodeType, string FromNodeId);
}
#nullable restore
