using LiteDB;
using Microsoft.Win32;
using NSwag;
using NSwag.CodeGeneration.CSharp;
using Nodify.Calculator.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Windows;

namespace Nodify.Calculator
{
    public class OperationsMenuViewModel : ObservableObject
    {
        private bool _isVisible;
        public bool IsVisible
        {
            get => _isVisible;
            set
            {
                SetProperty(ref _isVisible, value);
                if (!value)
                {
                    Closed?.Invoke();
                }
            }
        }

        private Point _location;
        public Point Location
        {
            get => _location;
            set => SetProperty(ref _location, value);
        }

        public event Action? Closed;

        public void OpenAt(Point targetLocation)
        {
            MenuAvailableOperations.Clear();
            MenuAvailableOperations.AddRange(AvailableOperations);
            Close();
            Location = targetLocation;
            IsVisible = true;
        }
        
        public void OpenOnlyGetSetVariable(Point targetLocation, string className)
        {
            var getVM = new OperationInfoViewModel()
            {
                Title = "GET",
                Type = OperationType.System,
                sysOp = SystemOperations.GET_SET,
                IsModelNode = true,
                ClassName = className,
            };
            var setVM = new OperationInfoViewModel()
            {
                Title = "SET",
                Type = OperationType.System,
                sysOp = SystemOperations.GET_SET,
                IsModelNode = true,
                ClassName = className
            };
            MenuAvailableOperations.Clear();
            MenuAvailableOperations.Add(getVM);
            MenuAvailableOperations.Add(setVM);
            Close();
            Location = targetLocation;
            IsVisible = true;
        }

        public void OpenGetSetForVariable(Point targetLocation, OperationInfoViewModel variableInfo)
        {
            var getVM = new OperationInfoViewModel()
            {
                Title = "GET",
                Type = OperationType.System,
                sysOp = SystemOperations.GET_SET,
                IsSimpleVariable = true,
                VariableType = variableInfo.VariableType,
                DefaultValue = variableInfo.DefaultValue,
                ClassName = variableInfo.Title ?? string.Empty
            };
            var setVM = new OperationInfoViewModel()
            {
                Title = "SET",
                Type = OperationType.System,
                sysOp = SystemOperations.GET_SET,
                IsSimpleVariable = true,
                VariableType = variableInfo.VariableType,
                DefaultValue = variableInfo.DefaultValue,
                ClassName = variableInfo.Title ?? string.Empty
            };
            MenuAvailableOperations.Clear();
            MenuAvailableOperations.Add(getVM);
            MenuAvailableOperations.Add(setVM);
            Close();
            Location = targetLocation;
            IsVisible = true;
        }


        public void Close()
        {
            IsVisible = false;
        }

        public NodifyObservableCollection<OperationInfoViewModel> MenuAvailableOperations { get; }
        public NodifyObservableCollection<OperationInfoViewModel> AvailableOperations { get; }
        public NodifyObservableCollection<OperationInfoViewModel> SwaggerOperations { get; }
        public NodifyObservableCollection<OperationInfoViewModel> AvailableModels { get; }
        public NodifyObservableCollection<OperationInfoViewModel> AvailableVariables { get; }
        public NodifyObservableCollection<OperationInfoViewModel> AvailableFunctions { get; }
        public INodifyCommand CreateOperationCommand { get; }

        [Newtonsoft.Json.JsonIgnore]
        [BsonIgnore]
        private readonly CalculatorViewModel _calculator;

        public OperationsMenuViewModel(CalculatorViewModel calculator)
        {
            _calculator = calculator;
            List<OperationInfoViewModel> operations = new List<OperationInfoViewModel>();
            AvailableModels = new NodifyObservableCollection<OperationInfoViewModel>();
            AvailableVariables = new NodifyObservableCollection<OperationInfoViewModel>();
            AvailableFunctions = new NodifyObservableCollection<OperationInfoViewModel>();
            LoadVariablesFromDb();

            var customModelDir = "CustomModels";
            Directory.CreateDirectory(customModelDir);
            var dirInfo = new DirectoryInfo(customModelDir);
            var allFiles = dirInfo.GetFiles("*.cs");
            foreach (var item in allFiles)
            {
                var flName = Path.GetFileNameWithoutExtension(item.Name);
                var opVInfo = new OperationInfoViewModel()
                {
                    Title = flName,
                    IsModelNode = true,
                    Type = OperationType.System,
                    sysOp = SystemOperations.GET_SET,
                    ClassName = flName
                };
                AvailableModels.Add(opVInfo);
            }

            operations.AddRange(OperationFactory.GetSystemNodes());
            //operations.AddRange(OperationFactory.GetOperationsInfo(typeof(OperationsContainer)));

            SwaggerOperations = new NodifyObservableCollection<OperationInfoViewModel>();
            LoadSwaggerNodesFromDb();

            operations.AddRange(SwaggerOperations);

            AvailableOperations = new NodifyObservableCollection<OperationInfoViewModel>(operations);
            MenuAvailableOperations = new NodifyObservableCollection<OperationInfoViewModel>(operations);
            CreateOperationCommand = new DelegateCommand<OperationInfoViewModel>(CreateOperation);
            ImportSwaggerCommand = new DelegateCommand(ImportSwagger);
            AddVariableCommand = new DelegateCommand(AddVariable);
            CreateFunctionCommand = new DelegateCommand(CreateFunction);
        }

        
        public void AddNewModel(OperationInfoViewModel opModel)
        {
            if (System.Windows.Application.Current.Dispatcher.CheckAccess())
            {
                // Add directly if on UI thread
                AvailableModels.Add(opModel);
            }
            else
            {
                // Otherwise, marshal the call to the UI thread
                System.Windows.Application.Current.Dispatcher.Invoke(() =>
                {
                    AvailableModels.Add(opModel);
                });
            }
        }

        public INodifyCommand ImportSwaggerCommand { get; }
        public INodifyCommand AddVariableCommand { get; }
        public INodifyCommand CreateFunctionCommand { get; }

        private void CreateFunction()
        {
            var dialog = new CreateFunctionDialog();
            dialog.Owner = System.Windows.Application.Current.MainWindow;
            if (dialog.ShowDialog() != true)
                return;

            var funcName = dialog.FunctionName;

            if (AvailableFunctions.Any(f => f.Title == funcName))
            {
                MessageBox.Show($"A function named '{funcName}' already exists.", "Duplicate Function", MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var funcInfo = new OperationInfoViewModel
            {
                Title = funcName,
                Type = OperationType.System,
                sysOp = SystemOperations.FUNCTION,
                IsFlowNode = true,
                IsFunction = true,
                FunctionInputs = new System.Collections.Generic.List<FunctionParameterInfo>(dialog.InputParameters),
                FunctionOutputs = new System.Collections.Generic.List<FunctionParameterInfo>(dialog.OutputParameters)
            };

            AvailableFunctions.Add(funcInfo);
            AvailableOperations.Add(funcInfo);
        }

        private void AddVariable()
        {
            var dialog = new AddVariableDialog();
            dialog.Owner = System.Windows.Application.Current.MainWindow;
            if (dialog.ShowDialog() != true)
                return;

            var varName = dialog.VariableName;
            var varType = dialog.VariableType;
            var defaultVal = dialog.DefaultValue;

            // Check for duplicate name
            if (AvailableVariables.Any(v => v.Title == varName))
            {
                MessageBox.Show($"A variable named '{varName}' already exists.", "Duplicate Variable", MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var varInfo = CreateVariableInfo(varName, varType, defaultVal);
            AvailableVariables.Add(varInfo);
            SaveVariableToDb(varName, varType, defaultVal);
        }

        private OperationInfoViewModel CreateVariableInfo(string name, string varType, string defaultValue)
        {
            return new OperationInfoViewModel
            {
                Title = name,
                Type = OperationType.System,
                sysOp = SystemOperations.GET_SET,
                IsSimpleVariable = true,
                VariableType = varType,
                DefaultValue = defaultValue,
                IsModelNode = false
            };
        }

        private void SaveVariableToDb(string name, string varType, string defaultValue)
        {
            using var db = new LiteDbHelper<VariableModel>("Variables");
            db.Insert(new VariableModel
            {
                Name = name,
                VariableType = varType,
                DefaultValue = defaultValue
            });
        }

        private void LoadVariablesFromDb()
        {
            try
            {
                using var db = new LiteDbHelper<VariableModel>("Variables");
                foreach (var v in db.FindAll())
                {
                    AvailableVariables.Add(CreateVariableInfo(v.Name, v.VariableType, v.DefaultValue));
                }
            }
            catch
            {
                // DB may not exist yet
            }
        }

        private void ImportSwagger()
        {
            var openFileDialog = new OpenFileDialog
            {
                Filter = "JSON files (*.json)|*.json|Text files (*.txt)|*.txt|All files (*.*)|*.*",
                Title = "Import Swagger JSON File"
            };

            if (openFileDialog.ShowDialog() != true)
                return;

            try
            {
                var nodes = ParseSwaggerFile(openFileDialog.FileName);
                var fileName = Path.GetFileName(openFileDialog.FileName);
                SaveSwaggerNodesToDb(nodes, fileName);

                foreach (var node in nodes)
                {
                    SwaggerOperations.Add(node);
                    AvailableOperations.Add(node);
                    MenuAvailableOperations.Add(node);
                }

                MessageBox.Show($"Successfully imported {nodes.Count} API endpoints from Swagger.", "Import Swagger", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Failed to parse Swagger file: {ex.Message}", "Import Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private List<OperationInfoViewModel> ParseSwaggerFile(string jsonFilePath)
        {
            var operations = new List<OperationInfoViewModel>();
            var openApiDocument = OpenApiDocument.FromFileAsync(jsonFilePath).Result;

            foreach (var path in openApiDocument.Paths)
            {
                foreach (var method in path.Value)
                {
                    var ovmodel = new OperationInfoViewModel
                    {
                        Title = path.Key,
                        OPType = method.Key,
                        Type = OperationType.API
                    };

                    var addedParams = new HashSet<string>();
                    foreach (var parameter in method.Value.Parameters)
                    {
                        if (addedParams.Add(parameter.Name))
                        {
                            ovmodel.Input.Add(parameter.Name);
                        }
                    }

                    ovmodel.Output.Add("");
                    operations.Add(ovmodel);
                }
            }

            return operations;
        }

        private void SaveSwaggerNodesToDb(List<OperationInfoViewModel> nodes, string swaggerFileName)
        {
            using var db = new LiteDbHelper<SwaggerNodeModel>("SwaggerNodes");
            db.DeleteMany(n => n.SwaggerFileName == swaggerFileName);

            foreach (var node in nodes)
            {
                db.Insert(new SwaggerNodeModel
                {
                    Title = node.Title ?? string.Empty,
                    OPType = node.OPType ?? string.Empty,
                    InputNames = new List<string>(node.Input),
                    SwaggerFileName = swaggerFileName
                });
            }
        }

        private void LoadSwaggerNodesFromDb()
        {
            try
            {
                using var db = new LiteDbHelper<SwaggerNodeModel>("SwaggerNodes");
                var savedNodes = db.FindAll();

                foreach (var saved in savedNodes)
                {
                    var ovmodel = new OperationInfoViewModel
                    {
                        Title = saved.Title,
                        OPType = saved.OPType,
                        Type = OperationType.API
                    };

                    foreach (var inputName in saved.InputNames)
                    {
                        ovmodel.Input.Add(inputName);
                    }

                    ovmodel.Output.Add("");
                    SwaggerOperations.Add(ovmodel);
                }
            }
            catch
            {
                // DB may not exist yet on first run
            }
        }

        private void CreateOperation(OperationInfoViewModel operationInfo)
        {
            OperationViewModel op = OperationFactory.GetOperation(operationInfo);
            op.Location = Location;

            _calculator.Operations.Add(op);

            var pending = _calculator.PendingConnection;
            if (pending.IsVisible)
            {
                var connector = pending.Source.IsInput ? op.Output.FirstOrDefault() : op.Input.FirstOrDefault();
                if (connector != null && _calculator.CanCreateConnection(pending.Source, connector))
                {
                    _calculator.CreateConnection(pending.Source, connector);
                }
            }
            Close();
        }
    }
}
