using System.Reflection;
using NodePipeline.Abstractions.Interfaces.Nodes;
using NodePipeline.Engine.CodeGeneration;
using NodePipeline.Engine.CodeGeneration.Abstractions.Models;
using NodePipeline.Engine.Tests.CodeGeneratorTests.Fixtures;
using NodePipeline.Engine.Tests.CodeGeneratorTests.Fixtures.MockNodes;

namespace NodePipeline.Engine.Tests.CodeGeneratorTests;

public class NodeFactoryGeneratorTests
{
    #region CreateNode

    [Fact]
    public void CreateNodeMethod_ContainsAllNodeCases()
    {
        // Arrange
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, "CreateNode");
        Assert.False(string.IsNullOrEmpty(methodCode), "CreateNode method not found in generated code.");

        // Act & Assert
        foreach (var node in model.Nodes)
        {
            var caseLabel = $"case var t when t == NodeNamePrefixSettings.TrimNodeType(\"{node.Type}\"):";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for node {node.Type} not found.");

            if (node.HasParameterlessConstructor)
            {
                Assert.Contains($"node = new {node.TypeNameFull}();", caseBlock);
                Assert.Contains("break;", caseBlock);
            }
            else
            {
                Assert.Contains(
                    "throw new NodeCanNotBeInitializedWithoutFactoryException(pipelineId, config.Type, config.Id);",
                    caseBlock);
            }
        }

        Assert.Contains("InitializeNodeFields(pipelineId, node, config.Id);", generatedCode);
        Assert.Contains("return node ?? throw new NodeWasNotInitializedException(pipelineId, config.Type, config.Id);",
            generatedCode);
    }

    #endregion

    #region GetAllPorts

    [Fact]
    public void GetAllPortsMethod_ShouldExistInGeneratedCode()
    {
        const string methodSignature =
            "public Dictionary<NodePortKey, NodePortInfo> GetAllPorts(string pipelineId, HashSet<string> nodeTypes, Dictionary<string, IEnumerable<string>> nodeList)";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    #endregion

    #region GetNodeInputPorts

    [Fact]
    public void GetNodeInputPortsMethod_ShouldExistInGeneratedCode()
    {
        const string methodSignature =
            "public IEnumerable<NodePortInfo> GetNodeInputPorts(string pipelineId, string nodeType, string nodeId)";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    #endregion

    #region ConnectPortsStruct

    [Fact]
    public void ConnectPortsStruct_ShouldExistInGeneratedCode()
    {
        const string methodSignatureV1 =
            "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";
        const string methodSignatureV2 =
            "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)\n         where T : struct";
        var methodSignatureV3 =
            "private void ConnectPortsStruct<T>(string pipelineId, string nodeId,string nodeType, INodeField<T>? nodeInput, string nodeInputName, Dictionary<string, InputSource> inputs,"
            + Environment.NewLine +
            "        string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool disallowNullableOutput = false, bool isRequired = false)"
            + Environment.NewLine + "        where T : struct";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.True(generatedCode.Contains(methodSignatureV1)
                    || generatedCode.Contains(methodSignatureV2)
                    || generatedCode.Contains(methodSignatureV3));
    }

    #endregion

    #region ConnectPortsClass

    [Fact]
    public void ConnectPortsClass_ShouldExistInGeneratedCode()
    {
        const string methodSignatureV1 =
            "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";
        const string methodSignatureV2 =
            "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)\n        where T : class";
        var methodSignatureV3 =
            "private void ConnectPortsClass<T>(string pipelineId, string nodeId,string nodeType, INodeField<T>? nodeInput, string nodeInputName, Dictionary<string, InputSource> inputs,"
            + Environment.NewLine +
            "        string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool disallowNullableOutput = false, bool isRequired = false)"
            + Environment.NewLine + "        where T : class";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);


        Assert.True(generatedCode.Contains(methodSignatureV1)
                    || generatedCode.Contains(methodSignatureV2)
                    || generatedCode.Contains(methodSignatureV3));
    }

    #endregion

    #region TryConnectPorts

    [Fact]
    public void TryConnectPorts_ShouldExistInGeneratedCode()
    {
        const string methodSignatureV1 =
            "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";
        const string methodSignatureV2 =
            "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)\n        where T : notnull";
        var methodSignatureV3 =
            "private PortConnectionInfo? TryConnectPorts<T>(string pipelineId, string nodeId, string nodeType, INodeField<T>? nodeInput, string nodeInputName,"
            + Environment.NewLine +
            "        Dictionary<string, InputSource> inputs, string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool isRequired = false)"
            + Environment.NewLine + "        where T : notnull";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.True(generatedCode.Contains(methodSignatureV1)
                    || generatedCode.Contains(methodSignatureV2)
                    || generatedCode.Contains(methodSignatureV3));
    }

    #endregion

    #region GetDefaultValue

    [Fact]
    public void GetDefaultValue_ShouldExistInGeneratedCode()
    {
        const string methodSignature = "private static T? GetDefaultValue<T>()";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    #endregion

    [Fact]
    public void PortConnectionInfo_ShouldBeDeclaredInGeneratedCode()
    {
        const string recordDeclaration =
            "private record PortConnectionInfo(bool Connected, object? OutputFieldObj, string FromPortName, string FromNodeType, string FromNodeId);";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(recordDeclaration, generatedCode);
    }

    #region InitializeNodeFields

    [Fact]
    public void InitializeNodeFieldsMethod_ShouldExistInGeneratedCode()
    {
        var methodSignature = "private void InitializeNodeFields(string pipelineId, INode node, string nodeId)";
        // var methodSignature = $"private static Dictionary<NodePortKey, NodePortInfo> Get__{GetNodeDescriptorFromMockNode(mockNodeType).TypeNameShortSanitized}__NodePorts(string nodeId)";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void InitializeNodeFieldsMethod_ContainsSwitchCases()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var methodName = "InitializeNodeFields";
        var tCountByNodes = new Dictionary<NodeDescriptor, int>();

        var generatedCode = NodeFactoryGenerator.Generate(model);
        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);

        foreach (var node in model.Nodes)
        {
            Assert.Contains($"case {node.TypeNameFull} t", methodCode);
            if (!tCountByNodes.TryGetValue(node, out var value)) tCountByNodes.Add(node, 1);
            else tCountByNodes[node] = ++value;
        }

        Assert.Contains(
            "default:" + Environment.NewLine +
            "                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);", methodCode);
        Assert.DoesNotContain(tCountByNodes.Values, q => q > 1);
    }

    #endregion

    #region InitializeNodeFieldsSpecific

    [Theory]
    [InlineData(typeof(ThreeParameterNode), "CreateThreeParameterNodeModel")]
    [InlineData(typeof(ThreePortNode), "CreateThreePortNodeModel")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificInitializeNodeFieldsMethod_ShouldExistInGeneratedCode(Type mockNodeType, string factoryMethod)
    {
        var nodeDescriptor = TestHelper.GetNodeDescriptorFromMockNode(mockNodeType);
        var methodSignature =
            $"private static void Initialize__{nodeDescriptor.TypeNameShortSanitized}__NodeFields({nodeDescriptor.TypeNameFull} node, string nodeId)";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Theory]
    [InlineData(typeof(ThreeParameterNode), "CreateThreeParameterNodeModel")]
    [InlineData(typeof(ThreePortNode), "CreateThreePortNodeModel")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificInitializeNodeFieldsMethod_InitializesAllFields(Type mockNodeType, string factoryMethod)
    {
        var nodeDescriptor = TestHelper.GetNodeDescriptorFromMockNode(mockNodeType);
        var methodName = $"Initialize__{nodeDescriptor.TypeNameShortSanitized}__NodeFields";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);
        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);

        foreach (var nodeField in nodeDescriptor.Fields)
        {
            var nodeFieldType = string.Concat(nodeField.ValueType, nodeField.IsNullableValueType ? "?" : string.Empty);
            var initNodeFieldCodeV1 =
                $"node.{nodeField.PropertyName} = new NodePipeline.Abstractions.Models.NodeField<{nodeFieldType}>()"
                + Environment.NewLine + "        {"
                + Environment.NewLine + $"            Name = \"{nodeField.FieldName}\","
                + Environment.NewLine +
                $"            Code = NodeFieldCodeGenerator.GenerateFieldCode(nodeId, \"{nodeField.FieldName}\", FieldDirection.{nodeField.Direction.ToString()}),"
                + Environment.NewLine +
                $"            Direction = FieldDirection.{nodeField.Direction.ToString()},"
                + Environment.NewLine + "        };";
            var initNodeFieldCodeV2 =
                $"node.{nodeField.PropertyName} = new NodePipeline.Abstractions.Models.NodeField<{nodeFieldType}>()"
                + Environment.NewLine + "        {"
                + Environment.NewLine + $"            Name = \"{nodeField.FieldName}\","
                + Environment.NewLine +
                $"            Code = NodeFieldCodeGenerator.GenerateFieldCode(nodeId, \"{nodeField.FieldName}\", FieldDirection.{nodeField.Direction.ToString()}),"
                + Environment.NewLine +
                $"            Direction = FieldDirection.{nodeField.Direction.ToString()},"
                + Environment.NewLine +
                "            // Description intentionally omitted for runtime"
                + Environment.NewLine + "        };";
            Assert.True(methodCode.Contains(initNodeFieldCodeV1) || methodCode.Contains(initNodeFieldCodeV2));
        }
    }

    #endregion

    #region GetParameterCodes

    [Fact]
    public void GetParameterCodesMethod_ContainsAllNodeCases()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, "GetParameterCodes");
        Assert.False(string.IsNullOrEmpty(methodCode), "GetParameterCodes method not found in generated code.");

        foreach (var node in model.Nodes)
        {
            var caseLabel = $"case var t when t == NodeNamePrefixSettings.TrimNodeType(\"{node.Type}\"):";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock),
                $"Case block for node {(string.IsNullOrEmpty(node.Type) ? node.TypeNameFull : node.Type)} not found.");

            Assert.Contains($"return GetParameterCodesFor__{node.TypeNameShortSanitized}__Node();", caseBlock);
        }
    }

    [Fact]
    public void Generate_GetParameterCodesMethod_ContainsAllNodeCasesWithCorrectReturnMethods()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, "GetParameterCodes");
        Assert.False(string.IsNullOrEmpty(methodCode), "GetParameterCodes method not found in generated code.");

        foreach (var node in model.Nodes)
        {
            var caseLabel = $"case var t when t == NodeNamePrefixSettings.TrimNodeType(\"{node.Type}\"):";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock),
                $"Case block for node {(string.IsNullOrEmpty(node.Type) ? node.TypeNameFull : node.Type)} not found.");

            var expectedMethodName = $"return GetParameterCodesFor__{node.TypeNameShortSanitized}__Node();";
            Assert.Contains(expectedMethodName, caseBlock);
        }

        Assert.Contains(
            "default:" + Environment.NewLine + "                throw new NodeTypeNotFoundException(nodeType);",
            methodCode);
    }

    #endregion

    #region GetParameterCodesSpecific

    [Theory]
    [InlineData(typeof(OneParameterNode), "CreateMultipleNodesModel")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificGetParameterCodesMethod_ShouldExistInGeneratedCode(Type mockNodeType, string factoryMethod)
    {
        var methodSignature =
            $"private IEnumerable<string> GetParameterCodesFor__{TestHelper.GetNodeDescriptorFromMockNode(mockNodeType).TypeNameShortSanitized}__Node()";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void SpecificGetParameterCodesMethod_HasCorrectName()
    {
        var nodeDescriptor = SimpleNamedNode.GetDescriptor();
        var methodSignature =
            $"private IEnumerable<string> GetParameterCodesFor__{nodeDescriptor.TypeNameShortSanitized}__Node()";
        var model = TestNodeModelFactory.CreateSimpleNamedNodeModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void SpecificGetParameterCodesMethod_ContainsAllParameters()
    {
        var methodName = $"GetParameterCodesFor__{typeof(ThreeParameterNode).FullName}__Node";
        var generatedCode = NodeFactoryGenerator.Generate(TestNodeModelFactory.CreateThreeParameterNodeModel());
        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found.");

        var model = TestNodeModelFactory.CreateThreeParameterNodeModel();
        var node = model.Nodes.First(n => n.Type == nameof(ThreeParameterNode));
        Assert.NotNull(node);
        var nodeParameters = node.Fields.Where(q => q.Direction == FieldDirection.Parameter);

        foreach (var param in nodeParameters) Assert.Contains($"\"{param.FieldName}\"", methodCode);
    }

    [Fact]
    public void SpecificGetParameterCodesMethod_ContainsAllParameterNamesWithAttributeSupport()
    {
        var model = TestNodeModelFactory.CreateThreeParameterNodeModel();
        var node = model.Nodes[0];
        var methodName = $"GetParameterCodesFor__{node.TypeNameShortSanitized}__Node";

        var generatedCode = NodeFactoryGenerator.Generate(model);
        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"Method {methodName} not found in generated code.");

        foreach (var param in node.Fields.Where(q => q.Direction == FieldDirection.Parameter))
            Assert.Contains($"\"{param.FieldName}\"", methodCode);
    }

    #endregion

    #region GetNodePorts

    [Fact]
    public void GetNodePortsMethod_ShouldExistInGeneratedCode()
    {
        const string methodSignature =
            "private Dictionary<NodePortKey, NodePortInfo> GetNodePorts(string pipelineId, string nodeType, string nodeId)";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void GetNodePortsMethod_ContainsAllNodeCases()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, "GetNodePorts");
        Assert.False(string.IsNullOrEmpty(methodCode), "GetNodePorts method not found in generated code.");

        foreach (var node in model.Nodes)
        {
            var nodeType = node.Type;
            var caseLabel = $"case var t when t == NodeNamePrefixSettings.TrimNodeType(\"{nodeType}\"):";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for node {nodeType} not found.");

            Assert.Contains($"return Get__{node.TypeNameShortSanitized}__NodePorts(nodeId);", caseBlock);
        }

        Assert.Contains(
            "default:" + Environment.NewLine +
            "                throw new NodeNotFoundException(pipelineId, nodeType, nodeId);", methodCode);
    }

    #endregion

    #region GetNodePortsSpecific

    [Theory]
    [InlineData(typeof(ThreeParameterNode), "CreateThreeParameterNodeModel")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificGetNodePortsMethod_ShouldExistInGeneratedCode(Type mockNodeType, string factoryMethod)
    {
        var methodSignature =
            $"private static Dictionary<NodePortKey, NodePortInfo> Get__{TestHelper.GetNodeDescriptorFromMockNode(mockNodeType).TypeNameShortSanitized}__NodePorts(string nodeId)";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void SpecificGetNodePortsMethod_ContainsAllNodeCases()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, "GetNodePorts");
        Assert.False(string.IsNullOrEmpty(methodCode), "GetNodePorts method not found in generated code.");

        foreach (var node in model.Nodes)
        {
            var nodeType = string.IsNullOrEmpty(node.Type) ? node.TypeNameFull : node.Type;
            var caseLabel = $"case var t when t == NodeNamePrefixSettings.TrimNodeType(\"{nodeType}\"):";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for node {nodeType} not found.");

            Assert.Contains($"return Get__{node.TypeNameShortSanitized}__NodePorts(nodeId);", caseBlock);
        }

        Assert.Contains(
            "default:" + Environment.NewLine +
            "                throw new NodeNotFoundException(pipelineId, nodeType, nodeId);", methodCode);
    }

    [Fact]
    public void SpecificGetNodePortsMethod_ContainsCorrectInputProperty()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        foreach (var node in model.Nodes)
        {
            var methodName = $"Get__{node.TypeNameShortSanitized}__NodePorts";
            var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
            Assert.False(string.IsNullOrEmpty(methodCode), $"Method {methodName} not found in generated code.");

            foreach (var field in node.Fields.Where(f => f.Direction is FieldDirection.Input or FieldDirection.Output))
            {
                var isInputString = (field.Direction == FieldDirection.Input).ToString().ToLower();
                Assert.Contains($"new NodePortKey(nodeId, \"{field.FieldName}\", {isInputString})", methodCode);
                var portInfoParameters = ExtractNodePortInfoParametersFromMethodCode(methodCode, field.FieldName);
                Assert.Equal(isInputString, portInfoParameters.ElementAtOrDefault(5));
            }
        }
    }

    [Fact]
    public void SpecificGetNodePortsMethod_ContainsCorrectPortValueType()
    {
        var model = TestNodeModelFactory.CreateThreePortNodeModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        foreach (var node in model.Nodes)
        {
            var methodName = $"Get__{node.TypeNameShortSanitized}__NodePorts";
            var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
            Assert.False(string.IsNullOrEmpty(methodCode), $"Method {methodName} not found in generated code.");

            foreach (var field in node.Fields.Where(f => f.Direction is FieldDirection.Input or FieldDirection.Output))
            {
                var portInfoParameters = ExtractNodePortInfoParametersFromMethodCode(methodCode, field.FieldName);
                Assert.Equal($"typeof({field.ValueType})", portInfoParameters.ElementAtOrDefault(1));
                Assert.Equal(field.IsNullableValueType.ToString().ToLower(), portInfoParameters.ElementAtOrDefault(2));
            }
        }
    }

    [Theory]
    [InlineData("CreateThreePortNodeModel")]
    [InlineData("CreateThreePortNode2Model")]
    public void SpecificGetNodePortsMethod_ContainsCorrectRequiredProperty(string factoryMethod)
    {
        var model = TestHelper.GetNodeModel(factoryMethod);
        var generatedCode = NodeFactoryGenerator.Generate(model);

        foreach (var node in model.Nodes)
        {
            var methodName = $"Get__{node.TypeNameShortSanitized}__NodePorts";
            var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
            Assert.False(string.IsNullOrEmpty(methodCode), $"Method {methodName} not found in generated code.");

            foreach (var field in node.Fields.Where(f => f.Direction is FieldDirection.Input or FieldDirection.Output))
            {
                var isRequired = field.Metadata?.IsRequired == true;
                var portInfoParameters = ExtractNodePortInfoParametersFromMethodCode(methodCode, field.FieldName);
                Assert.Equal(isRequired.ToString().ToLower(), portInfoParameters.ElementAtOrDefault(3));
            }
        }
    }

    [Theory]
    [InlineData("CreateThreePortNodeModel")]
    [InlineData("CreateThreePortNode2Model")]
    public void SpecificGetNodePortsMethod_ContainsCorrectDisallowNullableInputProperty(string factoryMethod)
    {
        var model = TestHelper.GetNodeModel(factoryMethod);
        var generatedCode = NodeFactoryGenerator.Generate(model);

        foreach (var node in model.Nodes)
        {
            var methodName = $"Get__{node.TypeNameShortSanitized}__NodePorts";
            var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
            Assert.False(string.IsNullOrEmpty(methodCode), $"Method {methodName} not found in generated code.");

            foreach (var field in node.Fields.Where(f => f.Direction is FieldDirection.Input or FieldDirection.Output))
            {
                var disallowNullableOutput = field.Metadata?.DisallowNullableOutput == true;
                var portInfoParameters = ExtractNodePortInfoParametersFromMethodCode(methodCode, field.FieldName);
                Assert.Equal(disallowNullableOutput.ToString().ToLower(), portInfoParameters.ElementAtOrDefault(4));
            }
        }
    }

    private static string[] ExtractNodePortInfoParameters(string nodePortInfoCode)
    {
        const string prefix = "new NodePortInfo(";
        if (nodePortInfoCode.Length < prefix.Length + 2) throw new Exception();
        var parametersString = nodePortInfoCode.Substring(prefix.Length, nodePortInfoCode.Length - (prefix.Length + 1));
        return parametersString.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
    }

    private static string[] ExtractNodePortInfoParametersFromMethodCode(string methodCode, string fieldName)
    {
        var portInfoIndex = methodCode.IndexOf($"new NodePortInfo(\"{fieldName}\"", StringComparison.Ordinal);
        var endIndex = methodCode.IndexOf(Environment.NewLine, portInfoIndex + 1, StringComparison.Ordinal);
        return ExtractNodePortInfoParameters(methodCode.Substring(portInfoIndex + 1, endIndex - portInfoIndex - 1));
    }

    #endregion

    #region ReadParameterValue

    [Fact]
    public void ReadParameterValue_ShouldExistInGeneratedCode()
    {
        const string methodSignature =
            "public object? ReadParameterValue(string pipelineId, string nodeId, string nodeType, string parameterName, string? valueString)";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void ReadParameterValue_ContainsAllNodeCases()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, "ReadParameterValue");
        Assert.False(string.IsNullOrEmpty(methodCode), "ReadParameterValue method not found in generated code.");

        foreach (var node in model.Nodes)
        {
            var nodeType = node.Type;
            var caseLabel = $"case var t when t == NodeNamePrefixSettings.TrimNodeType(\"{nodeType}\"):";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for node {nodeType} not found.");

            Assert.Contains(
                $"return ReadParameterValueFrom__{node.TypeNameShortSanitized}__Node(pipelineId, nodeType, nodeId, parameterName, valueString);",
                caseBlock);
        }

        Assert.Contains(
            "default:" + Environment.NewLine + "                throw new NodeTypeNotFoundException(nodeType);",
            methodCode);
    }

    #endregion

    #region ReadParameterValueSpecific

    [Theory]
    [InlineData(typeof(ThreeParameterNode), "CreateThreeParameterNodeModel")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificReadParameterValueMethod_ShouldExistInGeneratedCode(Type mockNodeType, string factoryMethod)
    {
        var methodSignature =
            $"private static object? ReadParameterValueFrom__{TestHelper.GetNodeDescriptorFromMockNode(mockNodeType).TypeNameShortSanitized}__Node(string pipelineId, string nodeType, string nodeId, string parameterName, string? valueString)";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void SpecificReadParameterValueMethod_ContainsAllParameters()
    {
        var nodeDescriptor = ThreeParameterNode.GetDescriptor();
        var methodName = $"ReadParameterValueFrom__{nodeDescriptor.TypeNameShortSanitized}__Node";

        var model = TestNodeModelFactory.CreateThreeParameterNodeModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found in generated code.");

        foreach (var parameter in nodeDescriptor.Fields.Where(f => f.Direction == FieldDirection.Parameter))
        {
            var parameterName = parameter.FieldName;
            var caseLabel = $"case \"{parameterName}\":";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for parameter {parameter} not found.");
        }

        Assert.Contains(
            "default:" + Environment.NewLine +
            "                throw new NodeParameterNotFoundException(pipelineId, parameterName, nodeType, nodeId);",
            methodCode);
    }

    [Fact]
    public void SpecificReadParameterValueMethod_ReturnsCallsMethodWithAppropriateType()
    {
        var nodeDescriptor = ThreeParameterNode.GetDescriptor();
        var methodName = $"ReadParameterValueFrom__{nodeDescriptor.TypeNameShortSanitized}__Node";

        var model = TestNodeModelFactory.CreateThreeParameterNodeModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found in generated code.");

        foreach (var parameter in nodeDescriptor.Fields.Where(f => f.Direction == FieldDirection.Parameter))
        {
            var parameterName = parameter.FieldName;
            var caseLabel = $"case \"{parameterName}\":";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for parameter {parameter} not found.");
            Assert.Contains($"return ParseParameter<{parameter.ValueType}>(valueString);", caseBlock);
        }
    }

    #endregion

    #region GetNodeOutputs

    [Fact]
    public void GetNodeOutputs_ShouldExistInGeneratedCode()
    {
        const string methodSignature =
            "private IReadOnlyDictionary<string, object> GetNodeOutputs(string pipelineId, string nodeId, INode node)";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void GetNodeOutputs_ContainsAllNodeCases()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, "GetNodeOutputs");
        Assert.False(string.IsNullOrEmpty(methodCode), "ReadParameterValue method not found in generated code.");

        foreach (var node in model.Nodes)
        {
            var @case = $"case {node.TypeNameFull} t";
            var caseLabel = methodCode[methodCode.IndexOf(@case, StringComparison.Ordinal)..].Split(':')[0];
            var tNumberString = caseLabel[@case.Length..];
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for node {node.Type} not found.");

            Assert.Contains($"return Get__{node.TypeNameShortSanitized}__NodeOutputs(nodeId, t{tNumberString});",
                caseBlock);
        }

        Assert.Contains(
            "default:" + Environment.NewLine +
            "                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);", methodCode);
    }

    #endregion

    #region GetNodeOutputsSpecific

    [Theory]
    [InlineData(typeof(ThreeParameterNode), "CreateThreeParameterNodeModel")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificGetNodeOutputsMethod_ShouldExistInGeneratedCode(Type mockNodeType, string factoryMethod)
    {
        var nodeDescriptor = TestHelper.GetNodeDescriptorFromMockNode(mockNodeType);
        var methodSignature =
            $"private IReadOnlyDictionary<string, object> Get__{nodeDescriptor.TypeNameShortSanitized}__NodeOutputs(string nodeId, {nodeDescriptor.TypeNameFull} node)";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void SpecificGetNodeOutputsMethod_ContainsAllOutputPorts()
    {
        var nodeDescriptor = ThreePortNode.GetDescriptor();
        var methodName = $"Get__{nodeDescriptor.TypeNameShortSanitized}__NodeOutputs";


        var model = TestNodeModelFactory.CreateThreePortNodeModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);
        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);

        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found in generated code.");
        foreach (var outputPort in nodeDescriptor.Fields.Where(f => f.Direction == FieldDirection.Output))
            Assert.Contains(
                $"{{ NodeFieldCodeGenerator.GenerateFieldCode(nodeId, \"{outputPort.FieldName}\", FieldDirection.Output), node.{outputPort.PropertyName} }},",
                methodCode);
    }

    #endregion

    #region SetNodeParametersValues

    [Fact]
    public void SetNodeParametersValues_ShouldExistInGeneratedCode()
    {
        const string methodSignature =
            "public void SetNodeParametersValues<TNode>(string pipelineId, TNode node, string nodeId, Dictionary<string, object?> parameters) where TNode : INode";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void SetNodeParametersValues_ContainsAllNodeCases()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, "SetNodeParametersValues");
        Assert.False(string.IsNullOrEmpty(methodCode), "SetNodeParametersValues method not found in generated code.");

        foreach (var node in model.Nodes)
        {
            var @case = $"case {node.TypeNameFull} t";
            var caseLabel = methodCode[methodCode.IndexOf(@case, StringComparison.Ordinal)..].Split(':')[0];
            var tNumberString = caseLabel[@case.Length..];
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for node {node.Type} not found.");

            var expectedString =
                $"Set__{node.TypeNameShortSanitized}__NodeParametersValues(t{tNumberString}, nodeId, parameters);"
                + Environment.NewLine + "                break;";
            Assert.Contains(expectedString, caseBlock);
        }

        Assert.Contains(
            "default:" + Environment.NewLine +
            "                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);", methodCode);
    }

    #endregion

    #region SetNodeParametersValuesSpecific

    [Theory]
    [InlineData(typeof(ThreeParameterNode), "CreateThreeParameterNodeModel")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificSetNodeParametersValuesMethod_ShouldExistInGeneratedCode(Type mockNodeType,
        string factoryMethod)
    {
        var nodeDescriptor = TestHelper.GetNodeDescriptorFromMockNode(mockNodeType);
        var methodSignature =
            $"private static void Set__{nodeDescriptor.TypeNameShortSanitized}__NodeParametersValues({nodeDescriptor.TypeNameFull} node, string nodeId, Dictionary<string, object?> parameters)";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void SpecificSetNodeParametersValuesMethod_ContainsAllParameters()
    {
        var nodeDescriptor = ThreeParameterNode.GetDescriptor();
        var methodName = $"Set__{nodeDescriptor.TypeNameShortSanitized}__NodeParametersValues";

        var model = TestNodeModelFactory.CreateThreeParameterNodeModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found in generated code.");

        foreach (var parameter in nodeDescriptor.Fields.Where(f => f.Direction == FieldDirection.Parameter))
        {
            // var ifString = methodCode[methodCode.IndexOf($"if (parameters.TryGetValue(\"{parameter.FieldName}\", out var val{i})")..]
            var ifString =
                methodCode[
                        methodCode.IndexOf($"if (parameters.TryGetValue(\"{parameter.FieldName}\", out var val",
                            StringComparison.Ordinal)..]
                    .Split(Environment.NewLine)[0];
            var valueNumberString =
                ifString[ifString.IndexOf("out var ", StringComparison.Ordinal)..].Split(')')[0][11..];
            var valueNumber = int.Parse(valueNumberString);
            // var parameterType = string.Concat(parameter.ValueType, parameter.IsNullableValueType ? "?" : string.Empty);
            var parameterType = parameter.ValueType;
            var setParameterCode =
                $"if (parameters.TryGetValue(\"{parameter.FieldName}\", out var val{valueNumber}) && val{valueNumber} is {parameterType} typedVal{valueNumber})"
                + Environment.NewLine +
                $"            node.{parameter.PropertyName}.Value = typedVal{valueNumberString};";
            Assert.Contains(setParameterCode, methodCode);
        }
    }

    #endregion

    #region GetParameterDefaultValue

    [Fact]
    public void GetParameterDefaultValue_ShouldExistInGeneratedCode()
    {
        const string methodSignature = "public object? GetParameterDefaultValue(string nodeType, string parameterName)";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void GetParameterDefaultValue_ContainsAllNodeCases()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var codeSubstring =
            generatedCode[
                generatedCode.IndexOf("public object? GetParameterDefaultValue(", StringComparison.Ordinal)..];
        var methodCode = TestHelper.ExtractMethod(codeSubstring, "GetParameterDefaultValue");
        Assert.False(string.IsNullOrEmpty(methodCode), "GetParameterDefaultValue method not found in generated code.");

        foreach (var node in model.Nodes)
        {
            var caseLabel = $"case var t when t == NodeNamePrefixSettings.TrimNodeType(\"{node.Type}\")";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for node {node.Type} not found.");

            Assert.Contains($"return GetParameterDefaultValueFor__{node.TypeNameShortSanitized}__Node(parameterName);",
                caseBlock);
        }

        Assert.Contains(
            "default:" + Environment.NewLine +
            "                throw new NodeParameterNotFoundException(parameterName, nodeType);", methodCode);
    }

    #endregion

    #region GetParameterDefaultValueSpecific

    [Theory]
    [InlineData(typeof(ThreeParameterNode), "CreateThreeParameterNodeModel")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificGetParameterDefaultValueMethod_ShouldExistInGeneratedCode(Type mockNodeType,
        string factoryMethod)
    {
        var methodSignature =
            $"private static object? GetParameterDefaultValueFor__{TestHelper.GetNodeDescriptorFromMockNode(mockNodeType).TypeNameShortSanitized}__Node(string parameterName)";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void SpecificGetParameterDefaultValueMethod_ContainsAllParameters()
    {
        var nodeDescriptor = ThreeParameterNode.GetDescriptor();
        var methodName = $"GetParameterDefaultValueFor__{nodeDescriptor.TypeNameShortSanitized}__Node";

        var model = TestNodeModelFactory.CreateThreeParameterNodeModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found in generated code.");

        foreach (var parameter in nodeDescriptor.Fields.Where(f => f.Direction == FieldDirection.Parameter))
        {
            var parameterName = parameter.FieldName;
            var caseLabel = $"case \"{parameterName}\":";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for parameter {parameter} not found.");
        }

        Assert.Contains(
            "default:" + Environment.NewLine +
            $"                throw new NodeParameterNotFoundException(parameterName, \"{nodeDescriptor.Type}\");",
            methodCode);
    }

    [Fact]
    public void SpecificGetParameterDefaultValueMethod_CallMethodWithAppropriateType()
    {
        var nodeDescriptor = ThreeParameterNode.GetDescriptor();
        var methodName = $"GetParameterDefaultValueFor__{nodeDescriptor.TypeNameShortSanitized}__Node";
        var model = TestNodeModelFactory.CreateThreeParameterNodeModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);
        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found in generated code.");

        foreach (var parameter in nodeDescriptor.Fields.Where(f => f.Direction == FieldDirection.Parameter))
        {
            var parameterName = parameter.FieldName;
            var caseLabel = $"case \"{parameterName}\":";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            //TODO: or without "?"?
            var parameterType = string.Concat(parameter.ValueType, parameter.IsNullableValueType ? "?" : string.Empty);
            if (parameter.Metadata?.DefaultValue == null)
                Assert.Contains($"return GetDefaultValue<{parameterType}>();", caseBlock);
        }
    }

    [Fact]
    public void SpecificGetParameterDefaultValueMethod_ReturnsCallsMethodWithAppropriateType()
    {
        var nodeDescriptor = ThreeParameterNode.GetDescriptor();
        var methodName = $"GetParameterDefaultValueFor__{nodeDescriptor.TypeNameShortSanitized}__Node";

        var model = TestNodeModelFactory.CreateThreeParameterNodeModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found in generated code.");
        var parametersWithDefaultValues = nodeDescriptor.Fields
            .Where(f => f is { Direction: FieldDirection.Parameter, Metadata.DefaultValue: not null });

        foreach (var parameter in parametersWithDefaultValues)
        {
            var parameterName = parameter.FieldName;
            var caseLabel = $"case \"{parameterName}\":";
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.Contains($"return {parameter.Metadata!.DefaultValue};", caseBlock);
        }
    }


    //TODO: test OneParameterWithoutParameterlessConstructorNode and parameter without ParameterlessConstructor

    #endregion

    #region GetNodeType

    [Fact]
    public void GetNodeType_ShouldExistInGeneratedCode()
    {
        const string methodSignature =
            "private static string GetNodeType(string pipelineId, string nodeId, INode node)";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void GetNodeType_ContainsAllNodeCases()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, "GetNodeType");
        Assert.False(string.IsNullOrEmpty(methodCode), "GetNodeType method not found in generated code.");

        foreach (var node in model.Nodes)
        {
            var @case = $"case {node.TypeNameFull}:";
            var caseLabel = methodCode[methodCode.IndexOf(@case, StringComparison.Ordinal)..].Split(':')[0];
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for node {node.Type} not found.");

            Assert.Contains($"return NodeNamePrefixSettings.TrimNodeType(\"{node.Type}\");", caseBlock);
        }

        Assert.Contains(
            "default:" + Environment.NewLine +
            "                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);", methodCode);
    }

    #endregion

    #region ConnectPorts

    [Fact]
    public void ConnectPorts_ShouldExistInGeneratedCode()
    {
        const string methodSignature =
            "public void ConnectPorts<TNode>(string pipelineId, TNode node, string nodeId, Dictionary<string, InputSource> inputs, IReadOnlyDictionary<string, INode> createdNodes) where TNode : INode";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void ConnectPorts_ContainsAllNodeCases()
    {
        var model = TestNodeModelFactory.CreateMultipleNodesModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, "ConnectPorts");
        Assert.False(string.IsNullOrEmpty(methodCode), "ConnectPorts method not found in generated code.");

        foreach (var node in model.Nodes)
        {
            var @case = $"case {node.TypeNameFull} t";
            var caseLabel = methodCode[methodCode.IndexOf(@case, StringComparison.Ordinal)..].Split(':')[0];
            var tNumberString = caseLabel[@case.Length..];
            var caseBlock = TestHelper.ExtractSwitchCase(methodCode, caseLabel);
            Assert.False(string.IsNullOrEmpty(caseBlock), $"Case block for node {node.Type} not found.");
            var caseText =
                $"Connect__{node.TypeNameShortSanitized}__NodePorts(pipelineId, t{tNumberString}, nodeId, inputs, createdNodes);"
                + Environment.NewLine + "                break;";
            Assert.Contains(caseText, caseBlock);
        }

        Assert.Contains(
            "default:" + Environment.NewLine +
            "                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);", methodCode);
    }

    #endregion

    #region ConnectPortsSpecific

    [Theory]
    [InlineData(typeof(ThreePortNode), "CreateThreePortNodeModel")]
    [InlineData(typeof(ThreePortNode2), "CreateThreePortNode2Model")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificConnectPortsMethod_ShouldExistInGeneratedCode(Type mockNodeType, string factoryMethod)
    {
        var nodeDescriptor = TestHelper.GetNodeDescriptorFromMockNode(mockNodeType);
        var methodSignature =
            $"private void Connect__{nodeDescriptor.TypeNameShortSanitized}__NodePorts(string pipelineId, {nodeDescriptor.TypeNameFull} node, string nodeId, Dictionary<string, InputSource> inputs, IReadOnlyDictionary<string, INode> createdNodes)";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void SpecificMethod_CallsBindingMethodForAllNodeOutputPorts()
    {
        var nodeDescriptor = ThreePortNode.GetDescriptor();
        var methodName = $"Connect__{nodeDescriptor.TypeNameShortSanitized}__NodePorts";

        var model = TestNodeModelFactory.CreateThreePortNodeModel();
        var generatedCode = NodeFactoryGenerator.Generate(model);

        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found in generated code.");
        foreach (var port in nodeDescriptor.Fields.Where(f =>
                     f.Direction == FieldDirection.Input || f.Direction == FieldDirection.Output))
        {
            var connectPortMethodCallStringV1 =
                $"ConnectPortsStruct(pipelineId, nodeId, \"{nodeDescriptor.TypeNameShort}\", node.{port.PropertyName}, nameof(node.{port.PropertyName}), inputs, \"{port.FieldName}\", createdNodes";
            var connectPortMethodCallStringV2 =
                $"ConnectPortsClass(pipelineId, nodeId, \"{nodeDescriptor.TypeNameShort}\", node.{port.PropertyName}, nameof(node.{port.PropertyName}), inputs, \"{port.FieldName}\", createdNodes";
            if (port.Direction == FieldDirection.Input)
                Assert.True(methodCode.Contains(connectPortMethodCallStringV1) ||
                            methodCode.Contains(connectPortMethodCallStringV2));
            else
                Assert.False(methodCode.Contains(connectPortMethodCallStringV1) ||
                             methodCode.Contains(connectPortMethodCallStringV2));
        }
    }

    [Theory]
    [InlineData(typeof(ThreePortNode), "CreateThreePortNodeModel")]
    [InlineData(typeof(ThreePortNode2), "CreateThreePortNode2Model")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificMethod_ConnectPortsMethodCallOfRequiredInputPortShouldHaveIsRequiredParameterSet(
        Type mockNodeType, string factoryMethod)
    {
        var nodeDescriptor = TestHelper.GetNodeDescriptorFromMockNode(mockNodeType);
        var methodName = $"Connect__{nodeDescriptor.TypeNameShortSanitized}__NodePorts";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);
        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found in generated code.");

        foreach (var port in nodeDescriptor.Fields.Where(f => f.Direction == FieldDirection.Input))
        {
            var connectPortMethodCallString =
                $"ConnectPortsStruct(pipelineId, nodeId, \"{nodeDescriptor.TypeNameShort}\", node.{port.PropertyName}, nameof(node.{port.PropertyName}), inputs, \"{port.FieldName}\",  createdNodes";
            var parametersString =
                methodCode[
                    (methodCode.IndexOf(connectPortMethodCallString, StringComparison.Ordinal) +
                     connectPortMethodCallString.Length)..].Split(';')[0];
            if (port.Metadata?.IsRequired == true) Assert.Contains("isRequired: true", parametersString);
        }
    }

    [Theory]
    [InlineData(typeof(ThreePortNode), "CreateThreePortNodeModel")]
    [InlineData(typeof(ThreePortNode2), "CreateThreePortNode2Model")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificMethod_ConnectPortsMethodCallOfRequiredInputPortShouldHaveDisallowNullableOutputParameterSet(
        Type mockNodeType, string factoryMethod)
    {
        var nodeDescriptor = TestHelper.GetNodeDescriptorFromMockNode(mockNodeType);
        var methodName = $"Connect__{nodeDescriptor.TypeNameShortSanitized}__NodePorts";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);
        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found in generated code.");

        foreach (var port in nodeDescriptor.Fields.Where(f => f.Direction == FieldDirection.Input))
        {
            var connectPortMethodCallStringV1 =
                $"ConnectPortsStruct(pipelineId, nodeId, \"{nodeDescriptor.TypeNameShort}\", node.{port.PropertyName}, nameof(node.{port.PropertyName}), inputs, \"{port.FieldName}\", createdNodes";
            var connectPortMethodCallStringV2 =
                $"ConnectPortsClass(pipelineId, nodeId, \"{nodeDescriptor.TypeNameShort}\", node.{port.PropertyName}, nameof(node.{port.PropertyName}), inputs, \"{port.FieldName}\", createdNodes";
            var parametersString = (methodCode.Contains(connectPortMethodCallStringV1)
                    ? methodCode[
                        (methodCode.IndexOf(connectPortMethodCallStringV1, StringComparison.Ordinal) +
                         connectPortMethodCallStringV1.Length)..]
                    : methodCode.Contains(connectPortMethodCallStringV2)
                        ? methodCode[
                            (methodCode.IndexOf(connectPortMethodCallStringV2, StringComparison.Ordinal) +
                             connectPortMethodCallStringV2.Length)..]
                        : null
                )?.Split(';')[0] ?? string.Empty;
            if (port.Metadata?.DisallowNullableOutput == true)
                Assert.True(parametersString.Contains("disallowNullableOutput:true") ||
                            parametersString.Contains("disallowNullableOutput: true"));
        }
    }

    [Theory]
    [InlineData(typeof(ThreePortNodeWithReferenceAndStructInputs),
        "CreateThreePortNodeWithReferenceAndStructInputsModel")]
    [InlineData(typeof(ThreePortNode), "CreateThreePortNodeModel")]
    [InlineData(typeof(ThreePortNode2), "CreateThreePortNode2Model")]
    [InlineData(typeof(SimpleNamedNode), "CreateSimpleNamedNodeModel")]
    public void SpecificMethod_CallAppropriateConnectPortsMethodBasedOnPortValueType(Type mockNodeType,
        string factoryMethod)
    {
        var nodeDescriptor = TestHelper.GetNodeDescriptorFromMockNode(mockNodeType);
        var methodName = $"Connect__{nodeDescriptor.TypeNameShortSanitized}__NodePorts";
        var model = TestHelper.GetNodeModel(factoryMethod);

        var generatedCode = NodeFactoryGenerator.Generate(model);
        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        Assert.False(string.IsNullOrEmpty(methodCode), $"{methodName} method not found in generated code.");

        foreach (var port in nodeDescriptor.Fields.Where(f => f.Direction == FieldDirection.Input))
        {
            var methodSuffix = port.IsValueReferenceType ? "Class" : "Struct";
            var connectPortMethodCallString =
                // $"ConnectPorts{methodSuffix}<{port.ValueType.TrimEnd('?')}>(pipelineId, nodeId, \"{nodeDescriptor.TypeNameShort}\", node.{port.PropertyName}, nameof(node.{port.PropertyName}), inputs, \"{port.FieldName}\", createdNodes";
                $"ConnectPorts{methodSuffix}(pipelineId, nodeId, \"{nodeDescriptor.TypeNameShort}\", node.{port.PropertyName}, nameof(node.{port.PropertyName}), inputs, \"{port.FieldName}\", createdNodes";
            Assert.Contains(connectPortMethodCallString, methodCode);
        }
    }

    #endregion

    #region ParseParameter

    [Fact]
    public void ParseParameter_ShouldExistInGeneratedCode()
    {
        const string methodSignature = "private static T? ParseParameter<T>(string? valueString)";
        var model = TestNodeModelFactory.CreateMultipleNodesModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);

        Assert.Contains(methodSignature, generatedCode);
    }

    [Fact]
    public void ParseParameter_ShouldContainAllEnumsMentionedInModelNodes()
    {
        const string methodName = "ParseParameter";
        var model = TestNodeModelFactory.CreateMultipleNodesWithEnumParametersModel();

        var generatedCode = NodeFactoryGenerator.Generate(model);
        var methodCode = TestHelper.ExtractMethod(generatedCode, methodName);
        var assemblyEnumTypes = Assembly.GetAssembly(GetType())!
            .GetTypes()
            .Where(q => q.IsEnum)
            .Select(q => q.FullName)
            .Distinct().ToHashSet();
        var allEnums = model.Nodes
            .SelectMany(q => q.Fields)
            .Where(q => q.Direction == FieldDirection.Parameter && assemblyEnumTypes.Contains(q.ValueType))
            .Select(q => q.ValueType)
            .Distinct();
        foreach (var enumTypeString in allEnums)
        {
            var expectedString =
                $"if (type == typeof({enumTypeString})) return (T)(object)Enum.Parse<{enumTypeString}>(valueString);";
            Assert.Contains(expectedString, methodCode);
        }
    }

    #endregion

    //service


    //TODO: test usings
    //TODO: test public properties
}