using Nodify.Calculator.Models;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows;

namespace Nodify.Calculator
{
    public static class GraphSerializer
    {
        public static void Save(CalculatorViewModel calculator)
        {
            var graph = new SaveGraphModel();
            var operations = calculator.Operations;
            var connections = calculator.Connections;

            // Ensure all nodes have IDs
            int idCounter = 1;
            foreach (var op in operations)
            {
                if (string.IsNullOrEmpty(op.NodeId))
                    op.NodeId = $"node_{idCounter++}";
            }

            // Serialize nodes
            foreach (var op in operations)
            {
                var nodeData = new NodeData
                {
                    NodeId = op.NodeId,
                    Title = op.Title ?? string.Empty,
                    LocationX = op.Location.X,
                    LocationY = op.Location.Y
                };

                switch (op)
                {
                    case TakeOperationViewModel take:
                        nodeData.NodeType = "System";
                        nodeData.SystemOp = take.SystemOperationType.ToString();
                        nodeData.NthIndex = take.NthIndex;
                        nodeData.IsRandom = take.IsRandom;
                        break;
                    case FunctionOperationViewModel func:
                        nodeData.NodeType = "Function";
                        nodeData.FunctionName = func.FunctionName;
                        foreach (var p in func.InputParameters)
                            nodeData.FunctionInputs.Add(new FunctionParamData { Name = p.Name, Type = p.Type });
                        foreach (var p in func.OutputParameters)
                            nodeData.FunctionOutputs.Add(new FunctionParamData { Name = p.Name, Type = p.Type });
                        break;
                    case SystemOperationViewModel sys:
                        nodeData.NodeType = "System";
                        nodeData.SystemOp = sys.SystemOperationType.ToString();
                        // For GET_SET nodes, derive ClassName from title
                        if (sys.SystemOperationType == SystemOperations.GET_SET)
                        {
                            var title = sys.Title ?? "";
                            if (title.StartsWith("GET ")) nodeData.ClassName = title.Substring(4).Trim();
                            else if (title.StartsWith("SET ")) nodeData.ClassName = title.Substring(4).Trim();
                        }
                        break;
                    case APIOperationViewModel api:
                        nodeData.NodeType = "API";
                        nodeData.OPType = api.OperationType;
                        nodeData.ResponseModelClassName = api.ResponseModelClassName;
                        break;
                    case ExpandoOperationViewModel:
                        nodeData.NodeType = "Expando";
                        break;
                    case CalculatorOperationViewModel:
                        nodeData.NodeType = "Calculator";
                        break;
                    default:
                        nodeData.NodeType = "Normal";
                        break;
                }

                // Save connector metadata for all nodes
                foreach (var c in op.Input)
                    nodeData.InputConnectors.Add(SerializeConnector(c));
                foreach (var c in op.Output)
                    nodeData.OutputConnectors.Add(SerializeConnector(c));

                graph.Nodes.Add(nodeData);
            }

            // Serialize connections
            foreach (var conn in connections)
            {
                var inputOp = conn.Input?.Operation;
                var outputOp = conn.Output?.Operation;
                if (inputOp == null || outputOp == null) continue;

                var inputIndex = inputOp.Input.IndexOf(conn.Input);
                var outputIndex = outputOp.Output.IndexOf(conn.Output);

                if (inputIndex < 0 || outputIndex < 0) continue;

                graph.Connections.Add(new ConnectionData
                {
                    TargetNodeId = inputOp.NodeId,
                    TargetConnectorIndex = inputIndex,
                    SourceNodeId = outputOp.NodeId,
                    SourceConnectorIndex = outputIndex
                });
            }

            // Save to LiteDB
            using var db = new LiteDbHelper<SaveGraphModel>("Graph");
            db.DeleteMany(_ => true);
            db.Insert(graph);
        }

        public static void Load(CalculatorViewModel calculator, OperationsMenuViewModel opsMenu)
        {
            SaveGraphModel graph;
            try
            {
                using var db = new LiteDbHelper<SaveGraphModel>("Graph");
                graph = db.FindAll().FirstOrDefault();
            }
            catch
            {
                return;
            }

            if (graph == null || graph.Nodes.Count == 0) return;

            // Clear existing
            calculator.Connections.Clear();
            calculator.Operations.Clear();

            calculator.IsLoading = true;
            try
            {

            var nodeMap = new Dictionary<string, OperationViewModel>();

            // Recreate nodes
            foreach (var nd in graph.Nodes)
            {
                OperationViewModel op = null;
                try
                {
                    op = RecreateNode(nd, opsMenu);
                }
                catch
                {
                    continue;
                }

                if (op == null) continue;

                op.NodeId = nd.NodeId;
                op.Location = new System.Windows.Point(nd.LocationX, nd.LocationY);
                nodeMap[nd.NodeId] = op;
                calculator.Operations.Add(op);
            }

            // Recreate connections
            foreach (var cd in graph.Connections)
            {
                if (!nodeMap.TryGetValue(cd.SourceNodeId, out var sourceOp)) continue;
                if (!nodeMap.TryGetValue(cd.TargetNodeId, out var targetOp)) continue;
                if (cd.SourceConnectorIndex >= sourceOp.Output.Count) continue;
                if (cd.TargetConnectorIndex >= targetOp.Input.Count) continue;

                var sourceConn = sourceOp.Output[cd.SourceConnectorIndex];
                var targetConn = targetOp.Input[cd.TargetConnectorIndex];

                calculator.Connections.Add(new ConnectionViewModel
                {
                    Input = targetConn,
                    Output = sourceConn
                });
            }
            }
            finally
            {
                calculator.IsLoading = false;
            }
        }

        private static OperationViewModel RecreateNode(NodeData nd, OperationsMenuViewModel opsMenu)
        {
            switch (nd.NodeType)
            {
                case "API":
                {
                    // Find matching swagger operation or create from saved data
                    var info = new OperationInfoViewModel
                    {
                        Title = nd.Title,
                        OPType = nd.OPType?.ToLower() ?? "get",
                        Type = OperationType.API,
                        ResponseModelClassName = nd.ResponseModelClassName ?? string.Empty
                    };
                    // Restore inputs from saved connector data (skip flow triangle at index 0)
                    foreach (var ic in nd.InputConnectors)
                    {
                        if (ic.Shape != "Triangle")
                            info.Input.Add(ic.Title);
                    }
                    info.Output.Add("");
                    return OperationFactory.GetOperation(info);
                }

                case "System":
                {
                    if (Enum.TryParse<SystemOperations>(nd.SystemOp, out var sysOp))
                    {
                        var info = new OperationInfoViewModel
                        {
                            Title = nd.Title,
                            Type = OperationType.System,
                            sysOp = sysOp
                        };

                        if (sysOp == SystemOperations.GET_SET && !string.IsNullOrEmpty(nd.ClassName))
                        {
                            info.IsModelNode = true;
                            info.ClassName = nd.ClassName;
                        }

                        // Set up inputs/outputs based on system operation type
                        switch (sysOp)
                        {
                            case SystemOperations.BEGIN:
                                info.Output.Add(""); // flow output only
                                break;
                            case SystemOperations.END:
                                info.Input.Add(""); // flow input only
                                break;
                            case SystemOperations.TAKE:
                                info.Input.Add("List");
                                break;
                            case SystemOperations.COPY:
                                info.Input.Add("");
                                break;
                            case SystemOperations.SPLIT:
                                info.Input.Add("");
                                break;
                            case SystemOperations.IF:
                                info.Output.Add("");
                                info.IsFlowNode = true;
                                break;
                            default:
                                info.Output.Add("");
                                info.IsFlowNode = true;
                                break;
                        }

                        var op = OperationFactory.GetOperation(info);

                        // Restore Take properties
                        if (op is TakeOperationViewModel takeOp)
                        {
                            takeOp.NthIndex = nd.NthIndex;
                            takeOp.IsRandom = nd.IsRandom;
                        }

                        // Restore dynamic connectors for SPLIT/COPY/TAKE from saved data
                        RestoreDynamicConnectors(op, nd);

                        return op;
                    }
                    return null;
                }

                case "Function":
                {
                    var info = new OperationInfoViewModel
                    {
                        Title = nd.FunctionName,
                        Type = OperationType.System,
                        sysOp = SystemOperations.FUNCTION,
                        IsFunction = true
                    };
                    foreach (var inp in nd.FunctionInputs)
                        info.FunctionInputs.Add(new FunctionParameterInfo { Name = inp.Name, Type = inp.Type });
                    foreach (var outp in nd.FunctionOutputs)
                        info.FunctionOutputs.Add(new FunctionParameterInfo { Name = outp.Name, Type = outp.Type });
                    info.Output.Add("");
                    return OperationFactory.GetOperation(info);
                }

                case "Expando":
                {
                    var info = new OperationInfoViewModel
                    {
                        Title = nd.Title,
                        Type = OperationType.Expando
                    };
                    // Restore input count from saved connectors
                    foreach (var ic in nd.InputConnectors)
                        info.Input.Add(ic.Title);
                    info.Output.Add("");
                    return OperationFactory.GetOperation(info);
                }

                default:
                {
                    var info = new OperationInfoViewModel
                    {
                        Title = nd.Title,
                        Type = OperationType.Normal
                    };
                    foreach (var ic in nd.InputConnectors)
                        info.Input.Add(ic.Title);
                    info.Output.Add("");
                    return OperationFactory.GetOperation(info);
                }
            }
        }

        private static void RestoreDynamicConnectors(OperationViewModel op, NodeData nd)
        {
            // For SPLIT nodes: if saved outputs have more than just the flow triangle,
            // restore the dynamic property outputs
            if (op is SystemOperationViewModel sysVm)
            {
                if (sysVm.SystemOperationType == SystemOperations.SPLIT)
                {
                    // Remove default non-triangle outputs, then add saved ones
                    var defaultNonTriangle = op.Output.Where(o => o.Shape != ConnectorShape.Triangle).ToList();
                    foreach (var d in defaultNonTriangle) op.Output.Remove(d);

                    foreach (var sc in nd.OutputConnectors)
                    {
                        if (sc.Shape == "Triangle") continue;
                        op.Output.Add(DeserializeConnector(sc, false));
                    }
                    // Update title from saved
                    sysVm.Title = nd.Title;
                }
                else if (sysVm.SystemOperationType == SystemOperations.COPY)
                {
                    // Restore adapted connectors
                    if (nd.InputConnectors.Count > 0 && nd.InputConnectors[0].Shape != "Circle")
                    {
                        // Input was adapted — restore
                        var savedInput = nd.InputConnectors[0];
                        if (op.Input.Count > 0)
                        {
                            var inp = op.Input[0];
                            inp.Shape = ParseShape(savedInput.Shape);
                            inp.ConnectorColor = Color.FromArgb(savedInput.ColorArgb);
                            inp.DataType = savedInput.DataType;
                        }
                    }

                    // Restore output connectors 
                    var existingOutputs = op.Output.ToList();
                    foreach (var eo in existingOutputs) op.Output.Remove(eo);
                    foreach (var sc in nd.OutputConnectors)
                    {
                        op.Output.Add(DeserializeConnector(sc, false));
                    }
                }
                else if (sysVm.SystemOperationType == SystemOperations.TAKE)
                {
                    // Restore adapted list input
                    foreach (var inp in op.Input)
                    {
                        if (!inp.IsTakeListConnector) continue;
                        var savedInp = nd.InputConnectors.FirstOrDefault(c => c.IsTakeListConnector);
                        if (savedInp != null)
                        {
                            inp.Shape = ParseShape(savedInp.Shape);
                            inp.ConnectorColor = Color.FromArgb(savedInp.ColorArgb);
                            inp.DataType = savedInp.DataType;
                        }
                    }
                    // Restore output
                    var existingOutputs = op.Output.ToList();
                    foreach (var eo in existingOutputs) op.Output.Remove(eo);
                    foreach (var sc in nd.OutputConnectors)
                    {
                        op.Output.Add(DeserializeConnector(sc, false));
                    }
                }
            }
        }

        private static ConnectorData SerializeConnector(ConnectorViewModel c)
        {
            return new ConnectorData
            {
                Title = c.Title ?? string.Empty,
                Shape = c.Shape.ToString(),
                ColorArgb = c.RawColor.ToArgb(),
                DataType = c.DataType ?? string.Empty,
                IsCopyConnector = c.IsCopyConnector,
                IsTakeListConnector = c.IsTakeListConnector
            };
        }

        private static ConnectorViewModel DeserializeConnector(ConnectorData cd, bool isInput)
        {
            return new ConnectorViewModel
            {
                Title = cd.Title,
                Shape = ParseShape(cd.Shape),
                ConnectorColor = Color.FromArgb(cd.ColorArgb),
                DataType = cd.DataType,
                IsCopyConnector = cd.IsCopyConnector,
                IsTakeListConnector = cd.IsTakeListConnector,
                IsInput = isInput
            };
        }

        private static ConnectorShape ParseShape(string shape)
        {
            return Enum.TryParse<ConnectorShape>(shape, out var s) ? s : ConnectorShape.Circle;
        }
    }
}
