using Hawkeye.VisionBuilder.Workflow.Configuration;
using Hawkeye.VisionBuilder.Workflow.Links;
using OpenCvSharp;
using Serilog;
using System.ComponentModel.Design.Serialization;
using System.Diagnostics;
using System.Drawing;
using System.Reflection.PortableExecutable;
using System.Text.Json;
using System.Xml;
using VisionBuilder.UI.Common.Processing;
using Size = OpenCvSharp.Size;

namespace Hawkeye.VisionBuilder.Workflow;

public class WorkflowList
{


    public IImageSource ImageSource { get; set; }
    public Mat? RecipeImage { get; set; }

    public WorkflowConfiguration Configuration { get; set; } = new WorkflowConfiguration();
    public List<BaseOperation> Operations { get; set; } = new List<BaseOperation>();
    public Context Context { get; set; } = new Context();

    private int _executionCounter = 0;

    public void Execute()
    {
        
        Context.GraphicsElements.Clear();
        UpdateConfigVariables();
        long total = 0;
        foreach (BaseOperation operation in Operations)
        {
            if (!operation.Enabled)
            {
                operation.Result=true;
                continue;
            }
            if (Context.CancellationToken.IsCancellationRequested) break;
            operation.Interpret(Context);
        }
        _executionCounter++;
        if (_executionCounter % 10 == 0)
            GC.Collect();
        
        
    }

    public BaseOperation GetById(Guid originId)
    {
        var operation = Operations.FirstOrDefault(x =>
            x.Id == originId);
        if(operation==null)return NoOrigin.Instance;
        return operation as BaseOperation;
    }

    public IHaveOrigin GetOriginById(Guid originId)
    {
        return GetById(originId) as IHaveOrigin ?? NoOrigin.Instance;
    }

    public void ExecuteTill(BaseOperation op)
    {
        
        Context.GraphicsElements.Clear();
        UpdateConfigVariables();
        foreach (BaseOperation operation in Operations)
        {
            if(!operation.Enabled)continue;
            if(operation == op) break;
            operation.Interpret(Context);
        }
    }
    public void ExecuteOne(BaseOperation operation)
    {
        Context.GraphicsElements.Clear();
        UpdateConfigVariables();
        try
        {
            
            operation.Interpret(Context);
        }
        catch (Exception e)
        {
            operation.SetError(e.Message);
        }
        
    }

    void UpdateConfigVariables()
    {
        BaseOperation.SetScriptValue("config", Configuration);
    }
    public void Save(BinaryWriter bw)
    {
        Configuration.Save(bw);
        bw.Write(Operations.Count);
        foreach (BaseOperation operation in Operations)
        {
            bw.Write(operation.GetType().AssemblyQualifiedName);
            operation.Save(bw);
        }
        if (RecipeImage == null)
        {
            var mat = new Mat(128, 128, MatType.CV_8UC3, Scalar.Gray);
            var image = mat.ToBytes();
            bw.Write(image.Length);
            bw.Write(image);
        }
        else
        {
            var mat = RecipeImage;
            var image = mat.Resize(new Size(128, 128)).ToBytes();
            bw.Write(image.Length);
            bw.Write(image);
        }

    }
    public event Action PythonMissing=delegate {  };
    public void Load(BinaryReader br, OperationDiscoveryService operationDiscoveryService)
    {
        Configuration.Load(br);
        if (!File.Exists(Configuration.PythonPath))
            PythonMissing();
        int count = br.ReadInt32();
        Operations.Clear();
        for (int i = 0; i < count; i++)
        {
            string operationType = br.ReadString();
            var type=Type.GetType(operationType);
            BaseOperation operation = operationDiscoveryService.CreateInstance(type);
            operation.Load(br);
            Operations.Add(operation);
        }
        int length = br.ReadInt32();
        var image = br.ReadBytes(length);
        RecipeImage = Mat.FromImageData(image);
        
    }

    public static WorkflowList LoadFromFile(string fileName)
    {
        using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
        using (var br = new BinaryReader(fs))
        {
            var workflowList = new WorkflowList();
            workflowList.Load(br, new OperationDiscoveryService(workflowList));
            return workflowList;
        }
    }

    public void SaveXML(XmlWriter writer)
    {
        writer.WriteStartElement("Workflow");

        // Save Configuration
        writer.WriteStartElement("Configuration");
        writer.WriteAttributeString("RuntimeCameraType", Configuration.RuntimeCameraType.ToString());
        writer.WriteAttributeString("DevelopmentCameraType", Configuration.DevelopmentCameraType.ToString());
        writer.WriteAttributeString("Outputs", Configuration.Outputs.ToString());
        writer.WriteAttributeString("EmulationPath", Configuration.EmulationPath);
        writer.WriteAttributeString("PythonPath", Configuration.PythonPath);
        writer.WriteAttributeString("ResultPin", Configuration.ResultPin.ToString());
        writer.WriteAttributeString("SerialPort", Configuration.SerialPort);
        writer.WriteAttributeString("Delay", Configuration.Delay.ToString());
        writer.WriteEndElement(); // Configuration

        // Save Operations
        writer.WriteStartElement("Operations");
        writer.WriteAttributeString("Count", Operations.Count.ToString());

        foreach (BaseOperation operation in Operations)
        {
            writer.WriteStartElement("Operation");
            writer.WriteAttributeString("Type", operation.GetType().AssemblyQualifiedName);
            operation.SaveXML(writer);
            writer.WriteEndElement(); // Operation
        }
        writer.WriteEndElement(); // Operations

        // Save Recipe Image
        writer.WriteStartElement("RecipeImage");
        if (RecipeImage == null)
        {
            var mat = new Mat(128, 128, MatType.CV_8UC3, Scalar.Gray);
            var image = mat.ToBytes();
            writer.WriteAttributeString("Length", image.Length.ToString());
            writer.WriteBase64(image, 0, image.Length);
        }
        else
        {
            var mat = RecipeImage;
            var image = mat.Resize(new Size(128, 128)).ToBytes();
            writer.WriteAttributeString("Length", image.Length.ToString());
            writer.WriteBase64(image, 0, image.Length);
        }
        writer.WriteEndElement(); // RecipeImage

        writer.WriteEndElement(); // Workflow
    }

    public void LoadXML(XmlReader reader, OperationDiscoveryService operationDiscoveryService)
    {
        while (reader.Read())
        {
            if (reader.NodeType == XmlNodeType.Element)
            {
                switch (reader.Name)
                {
                    case "Configuration":
                        LoadConfigurationXML(reader);
                        break;
                    case "Operations":
                        LoadOperationsXML(reader, operationDiscoveryService);
                        break;
                    case "RecipeImage":
                        LoadRecipeImageXML(reader);
                        break;
                }
            }
        }
    }

    private void LoadConfigurationXML(XmlReader reader)
    {
        if (reader.GetAttribute("RuntimeCameraType") != null)
            Configuration.RuntimeCameraType = Enum.Parse<ECameraType>(reader.GetAttribute("RuntimeCameraType"));
        if (reader.GetAttribute("DevelopmentCameraType") != null)
            Configuration.DevelopmentCameraType = Enum.Parse<ECameraType>(reader.GetAttribute("DevelopmentCameraType"));
        if (reader.GetAttribute("Outputs") != null)
            Configuration.Outputs = Enum.Parse<EOutputs>(reader.GetAttribute("Outputs"));
        if (reader.GetAttribute("EmulationPath") != null)
            Configuration.EmulationPath = reader.GetAttribute("EmulationPath");
        if (reader.GetAttribute("PythonPath") != null)
            Configuration.PythonPath = reader.GetAttribute("PythonPath");
        if (reader.GetAttribute("ResultPin") != null)
            Configuration.ResultPin = int.Parse(reader.GetAttribute("ResultPin"));
        if (reader.GetAttribute("SerialPort") != null)
            Configuration.SerialPort = reader.GetAttribute("SerialPort");
        if (reader.GetAttribute("Delay") != null)
            Configuration.Delay = int.Parse(reader.GetAttribute("Delay"));
    }

    private void LoadOperationsXML(XmlReader reader, OperationDiscoveryService operationDiscoveryService)
    {
        Operations.Clear();

        while (reader.Read())
        {
            if (reader.NodeType == XmlNodeType.Element && reader.Name == "Operation")
            {
                string operationType = reader.GetAttribute("Type");
                var type = Type.GetType(operationType);
                if (type != null)
                {
                    BaseOperation operation = operationDiscoveryService.CreateInstance(type);
                    // go to child elements of Operation
                    operation.LoadXML(reader);
                    Operations.Add(operation);
                }
            }
            else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Operations")
            {
                break;
            }
        }
    }

    private void LoadRecipeImageXML(XmlReader reader)
    {
        if (reader.GetAttribute("Length") != null)
        {
            int length = int.Parse(reader.GetAttribute("Length"));
            byte[] imageData = new byte[length];
            int bytesRead = reader.ReadElementContentAsBase64(imageData, 0, length);
            if (bytesRead > 0)
            {
                RecipeImage = Mat.FromImageData(imageData);
            }
        }
    }

    private Dictionary<string, object> ConvertJsonElementsToNatives(Dictionary<string, object> dict)
    {
        var result = new Dictionary<string, object>();
        
        foreach (var kvp in dict)
        {
            result[kvp.Key] = ConvertJsonElementToNative(kvp.Value);
        }
        
        return result;
    }

    private object ConvertJsonElementToNative(object value)
    {
        if (value is JsonElement element)
        {
            return element.ValueKind switch
            {
                JsonValueKind.String => element.GetString(),
                JsonValueKind.Number => element.TryGetInt32(out var intVal) ? intVal :
                                      element.TryGetDouble(out var doubleVal) ? doubleVal : 
                                      element.GetDecimal(),
                JsonValueKind.True => true,
                JsonValueKind.False => false,
                JsonValueKind.Null => null,
                JsonValueKind.Object => ConvertJsonElementsToNatives(
                    JsonSerializer.Deserialize<Dictionary<string, object>>(element.GetRawText())),
                JsonValueKind.Array => element.EnumerateArray()
                    .Select(x => ConvertJsonElementToNative(x)).ToArray(),
                _ => value
            };
        }
        else if (value is Dictionary<string, object> nestedDict)
        {
            return ConvertJsonElementsToNatives(nestedDict);
        }
        
        return value;
    }

    public void SaveJSON(string fileName)
    {
        var workflowData = new Dictionary<string, object>();

        // Save Configuration
        var configData = new Dictionary<string, object>
        {
            ["RuntimeCameraType"] = Configuration.RuntimeCameraType.ToString(),
            ["DevelopmentCameraType"] = Configuration.DevelopmentCameraType.ToString(),
            ["Outputs"] = Configuration.Outputs.ToString(),
            ["EmulationPath"] = Configuration.EmulationPath,
            ["PythonPath"] = Configuration.PythonPath,
            ["ResultPin"] = Configuration.ResultPin,
            ["SerialPort"] = Configuration.SerialPort,
            ["Delay"] = Configuration.Delay
        };
        workflowData["Configuration"] = configData;

        // Save Operations
        var operationsData = new List<Dictionary<string, object>>();
        foreach (BaseOperation operation in Operations)
        {
            var operationData = new Dictionary<string, object>
            {
                ["Type"] = operation.GetType().AssemblyQualifiedName
            };
            
            var operationDict = new Dictionary<string, object>();
            operation.Save(operationDict);
            operationData["Data"] = operationDict;
            
            operationsData.Add(operationData);
        }
        workflowData["Operations"] = operationsData;

        // Save Recipe Image
        if (RecipeImage == null)
        {
            var mat = new Mat(128, 128, MatType.CV_8UC3, Scalar.Gray);
            var image = mat.ToBytes();
            workflowData["RecipeImage"] = Convert.ToBase64String(image);
        }
        else
        {
            var mat = RecipeImage;
            var image = mat.Resize(new Size(128, 128)).ToBytes();
            workflowData["RecipeImage"] = Convert.ToBase64String(image);
        }

        var options = new JsonSerializerOptions 
        { 
            WriteIndented = true 
        };
        string jsonString = JsonSerializer.Serialize(workflowData, options);
        File.WriteAllText(fileName, jsonString);
    }

    public void LoadJSON(string fileName, OperationDiscoveryService operationDiscoveryService)
    {
        string jsonString = File.ReadAllText(fileName);
        var workflowData = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonString);

        if (workflowData == null) return;

        // Load Configuration
        if (workflowData.ContainsKey("Configuration"))
        {
            var configElement = (JsonElement)workflowData["Configuration"];
            var configData = JsonSerializer.Deserialize<Dictionary<string, object>>(configElement.GetRawText());
            
            if (configData != null)
            {
                var convertedConfigData = ConvertJsonElementsToNatives(configData);
                
                if (convertedConfigData.ContainsKey("RuntimeCameraType"))
                    Configuration.RuntimeCameraType = Enum.Parse<ECameraType>(convertedConfigData["RuntimeCameraType"].ToString());
                if (convertedConfigData.ContainsKey("DevelopmentCameraType"))
                    Configuration.DevelopmentCameraType = Enum.Parse<ECameraType>(convertedConfigData["DevelopmentCameraType"].ToString());
                if (convertedConfigData.ContainsKey("Outputs"))
                    Configuration.Outputs = Enum.Parse<EOutputs>(convertedConfigData["Outputs"].ToString());
                if (convertedConfigData.ContainsKey("EmulationPath"))
                    Configuration.EmulationPath = convertedConfigData["EmulationPath"].ToString();
                if (convertedConfigData.ContainsKey("PythonPath"))
                    Configuration.PythonPath = convertedConfigData["PythonPath"].ToString();
                if (convertedConfigData.ContainsKey("ResultPin"))
                    Configuration.ResultPin = (int)convertedConfigData["ResultPin"];
                if (convertedConfigData.ContainsKey("SerialPort"))
                    Configuration.SerialPort = convertedConfigData["SerialPort"].ToString();
                if (convertedConfigData.ContainsKey("Delay"))
                    Configuration.Delay = (int)convertedConfigData["Delay"];
            }
        }

        if (!File.Exists(Configuration.PythonPath))
            PythonMissing();

        // Load Operations
        Operations.Clear();
        if (workflowData.ContainsKey("Operations"))
        {
            var operationsElement = (JsonElement)workflowData["Operations"];
            var operationsArray = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(operationsElement.GetRawText());
            
            if (operationsArray != null)
            {
                foreach (var operationData in operationsArray)
                {
                    if (operationData.ContainsKey("Type") && operationData.ContainsKey("Data"))
                    {
                        string operationType = operationData["Type"].ToString();
                        var type = Type.GetType(operationType);
                        if (type != null)
                        {
                            BaseOperation operation = operationDiscoveryService.CreateInstance(type);
                            
                            var dataElement = (JsonElement)operationData["Data"];
                            var operationDict = JsonSerializer.Deserialize<Dictionary<string, object>>(dataElement.GetRawText());
                            
                            if (operationDict != null)
                            {
                                var convertedDict = ConvertJsonElementsToNatives(operationDict);
                                operation.Load(convertedDict);
                            }
                            
                            Operations.Add(operation);
                        }
                    }
                }
            }
        }

        // Load Recipe Image
        if (workflowData.ContainsKey("RecipeImage"))
        {
            string base64Image = workflowData["RecipeImage"].ToString();
            if (!string.IsNullOrEmpty(base64Image))
            {
                byte[] imageData = Convert.FromBase64String(base64Image);
                RecipeImage = Mat.FromImageData(imageData);
            }
        }
    }

    public static WorkflowList LoadJSONFromFile(string fileName)
    {
        var workflowList = new WorkflowList();
        workflowList.LoadJSON(fileName, new OperationDiscoveryService(workflowList));
        return workflowList;
    }

    public void HotReloadParameters(string fileName)
    {
        string jsonString = File.ReadAllText(fileName);
        var workflowData = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonString);

        if (workflowData == null) return;

        // Update Configuration
        if (workflowData.ContainsKey("Configuration"))
        {
            var configElement = (JsonElement)workflowData["Configuration"];
            var configData = JsonSerializer.Deserialize<Dictionary<string, object>>(configElement.GetRawText());

            if (configData != null)
            {
                var convertedConfigData = ConvertJsonElementsToNatives(configData);

                if (convertedConfigData.ContainsKey("RuntimeCameraType"))
                    Configuration.RuntimeCameraType = Enum.Parse<ECameraType>(convertedConfigData["RuntimeCameraType"].ToString());
                if (convertedConfigData.ContainsKey("DevelopmentCameraType"))
                    Configuration.DevelopmentCameraType = Enum.Parse<ECameraType>(convertedConfigData["DevelopmentCameraType"].ToString());
                if (convertedConfigData.ContainsKey("Outputs"))
                    Configuration.Outputs = Enum.Parse<EOutputs>(convertedConfigData["Outputs"].ToString());
                if (convertedConfigData.ContainsKey("EmulationPath"))
                    Configuration.EmulationPath = convertedConfigData["EmulationPath"].ToString();
                if (convertedConfigData.ContainsKey("PythonPath"))
                    Configuration.PythonPath = convertedConfigData["PythonPath"].ToString();
                if (convertedConfigData.ContainsKey("ResultPin"))
                    Configuration.ResultPin = (int)convertedConfigData["ResultPin"];
                if (convertedConfigData.ContainsKey("SerialPort"))
                    Configuration.SerialPort = convertedConfigData["SerialPort"].ToString();
                if (convertedConfigData.ContainsKey("Delay"))
                    Configuration.Delay = (int)convertedConfigData["Delay"];
            }
        }

        // Update operation parameters by matching Id — do NOT recreate the Operations list
        if (workflowData.ContainsKey("Operations"))
        {
            var operationsElement = (JsonElement)workflowData["Operations"];
            var operationsArray = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(operationsElement.GetRawText());

            if (operationsArray != null)
            {
                foreach (var operationData in operationsArray)
                {
                    if (!operationData.ContainsKey("Data")) continue;

                    var dataElement = (JsonElement)operationData["Data"];
                    var operationDict = JsonSerializer.Deserialize<Dictionary<string, object>>(dataElement.GetRawText());

                    if (operationDict == null) continue;

                    var convertedDict = ConvertJsonElementsToNatives(operationDict);

                    if (!convertedDict.ContainsKey("Id")) continue;

                    var id = new Guid(convertedDict["Id"].ToString());
                    var existingOp = Operations.FirstOrDefault(x => x.Id == id);

                    if (existingOp != null)
                    {
                        existingOp.Load(convertedDict);
                    }
                    else
                    {
                        Log.Warning("HotReload: no matching operation for Id {Id}", id);
                    }
                }
            }
        }
    }

}