using Adaptation.Eaf.Management.ConfigurationData.CellAutomation;
using Adaptation.FileHandlers;
using Adaptation.Ifx.Eaf.EquipmentConnector.File.Configuration;
using Adaptation.Shared;
using Adaptation.Shared.Methods;
using Eaf.Core;
using Eaf.Core.Smtp;
using Eaf.EquipmentCore.DataCollection.Reporting;
using Eaf.EquipmentCore.SelfDescription.ElementDescription;
using Eaf.EquipmentCore.SelfDescription.EquipmentStructure;
using Eaf.EquipmentCore.SelfDescription.ParameterTypes;
using Ifx.Eaf.EquipmentConnector.File.Component.Reader;
using Ifx.Eaf.EquipmentConnector.File.SelfDescription;
using log4net;
using Shared;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;

namespace EC.FileHandlers;

public partial class FileRead : FileReaderHandler, ISMTP
{

    private readonly ILog _Log;
    private IFileRead _FileRead;
    private EquipmentEvent _EquipmentEvent;
    private readonly bool _UseCyclicalForDescription;
    private FilePathGenerator _FilePathGeneratorForError;
    private FilePathGenerator _FilePathGeneratorForTarget;
    private readonly List<EquipmentParameter> _EquipmentParameters;
    private static readonly Dictionary<string, List<long>> _DummyRuns;
    private static readonly Dictionary<long, List<Adaptation.Shared.Metrology.WS.Results>> _StaticRuns;

    static FileRead()
    {
        _DummyRuns = new();
        _StaticRuns = new();
    }

    public FileRead()
    {
        if (FileParameter is null)
            FileParameter = new Dictionary<string, string>();
        _FileRead = null;
        _UseCyclicalForDescription = false;
        _Log = LogManager.GetLogger(typeof(FileRead));
        _EquipmentParameters = new List<EquipmentParameter>();
    }

    public override void CreateSelfDescription()
    {
        MethodBase methodBase = new StackFrame().GetMethod();
        _Log.Debug(string.Concat(methodBase.Name, " - Entry"));
        try
        {
            if (Equipment is null)
                throw new Exception();
            CreateSelfDescription(methodBase);
            _Log.Debug(string.Concat(methodBase.Name, " - Try - Exit"));
        }
        catch (Exception ex)
        {
            _Log.Error(string.Concat(methodBase.Name, " - Catch - Entry {", ex.Message, "}", Environment.NewLine, Environment.NewLine, ex.StackTrace));
            long breakAfter = DateTime.Now.AddSeconds(30).Ticks;
            for (short i = 0; i < short.MaxValue; i++)
            {
                if (DateTime.Now.Ticks > breakAfter)
                    break;
                Thread.Sleep(500);
            }
            throw;
        }
    }

    public override bool Extract(string reportFullPath, string eventName)
    {
        MethodBase methodBase = new StackFrame().GetMethod();
        _Log.Debug(string.Concat(methodBase.Name, " - Entry - {", reportFullPath, "}"));
        Tuple<string, Test[], JsonElement[], List<FileInfo>> extractResults = null;
        try
        {
            extractResults = _FileRead.GetExtractResult(reportFullPath, eventName);
            TriggerEvents(extractResults);
            _FileRead.Move(extractResults);
            FilePathGeneratorInfoMove(extractResults);
            _FileRead.WaitForThread();
            _Log.Debug(string.Concat(methodBase.Name, " - Try - Exit"));
            return true;
        }
        catch (Exception ex)
        {
            _Log.Error(string.Concat(methodBase.Name, " - Catch - Entry {", ex.Message, "}", Environment.NewLine, Environment.NewLine, ex.StackTrace));
            _FileRead.Move(extractResults, exception: ex);
            FilePathGeneratorInfoMove(extractResults, exception: ex);
            return false;
        }
    }

    private void Send(EmailMessage emailMessage)
    {
        ISmtp smtp = Backbone.Instance.GetBackboneComponentsOfType<ISmtp>().SingleOrDefault();
        if (smtp is not null)
            smtp.Send(emailMessage);
    }

    void ISMTP.SendLowPriorityEmailMessage(string subject, string body)
    {
        EmailMessage emailMessage = new(subject, body, MailPriority.Low);
        Send(emailMessage);
    }

    void ISMTP.SendHighPriorityEmailMessage(string subject, string body)
    {
        EmailMessage emailMessage = new(subject, body, MailPriority.High);
        Send(emailMessage);
    }

    void ISMTP.SendNormalPriorityEmailMessage(string subject, string body)
    {
        EmailMessage emailMessage = new(subject, body, MailPriority.Normal);
        Send(emailMessage);
    }

    private void AddParameterRangeAndEvent()
    {
        string description;
        bool allowNull = false;
        EquipmentParameter equipmentParameter;
        JsonProperty[] jsonProperties = _FileRead.GetDefault();
        Dictionary<string, string> keyValuePairs = _FileRead.GetDisplayNamesJsonElement();
        Dictionary<JsonValueKind, ParameterTypeDefinition> parameterTypeDefinitions = new();
        FileConnectorParameterTypeDefinitionProvider fileConnectorParameterTypeDefinitionProvider = new();
        foreach (ParameterTypeDefinition parameterTypeDefinition in fileConnectorParameterTypeDefinitionProvider.GetAllParameterTypeDefinition())
        {
            switch (parameterTypeDefinition.Name)
            {
                case nameof(String):
                    parameterTypeDefinitions.Add(JsonValueKind.String, parameterTypeDefinition);
                    break;
                case nameof(Double):
                    parameterTypeDefinitions.Add(JsonValueKind.Number, parameterTypeDefinition);
                    break;
                case nameof(Boolean):
                    parameterTypeDefinitions.Add(JsonValueKind.True, parameterTypeDefinition);
                    parameterTypeDefinitions.Add(JsonValueKind.False, parameterTypeDefinition);
                    break;
                default:
                    break;
            }
        }
        foreach (JsonProperty jsonProperty in jsonProperties)
        {
            if (jsonProperty.Value.ValueKind == JsonValueKind.Object && !parameterTypeDefinitions.ContainsKey(JsonValueKind.Object))
            {
                StructuredType structuredType = new(nameof(StructuredType), string.Empty, new List<Field>());
                parameterTypeDefinitions.Add(JsonValueKind.Object, structuredType);
                Equipment.SelfDescriptionBuilder.AddParameterTypeDefinition(structuredType);
            }
            if (!parameterTypeDefinitions.ContainsKey(jsonProperty.Value.ValueKind))
                throw new Exception(string.Concat('<', jsonProperty.Name, "> {", jsonProperty.Value.ValueKind, "} is not mapped!"));
        }
        foreach (JsonProperty jsonProperty in jsonProperties)
        {
            if (jsonProperty.Value.ValueKind == JsonValueKind.Null && !allowNull)
                throw new Exception();
            if (jsonProperty.Value.ValueKind != JsonValueKind.String || !keyValuePairs.ContainsKey(jsonProperty.Name))
                description = string.Empty;
            else
                description = keyValuePairs[jsonProperty.Name].Split('|')[0];
            equipmentParameter = new EquipmentParameter(jsonProperty.Name, parameterTypeDefinitions[jsonProperty.Value.ValueKind], description);
            _EquipmentParameters.Add(equipmentParameter);
        }
        _ = Equipment.SelfDescriptionBuilder.RootEquipmentElementBuilder.AddParameterRange(_EquipmentParameters);
        _EquipmentEvent = new EquipmentEvent(_FileRead.EventName, _FileRead.GetEventDescription(), _EquipmentParameters);
        _ = Equipment.SelfDescriptionBuilder.RootEquipmentElementBuilder.AddEvent(_EquipmentEvent);
    }

    private void CreateSelfDescription(MethodBase methodBase)
    {
        string cellInstanceName;
        string equipmentTypeName = string.Empty;
        string equipmentDictionaryName = string.Empty;
        EquipmentElement equipmentElement = Equipment.SelfDescriptionBuilder.RootEquipmentElementBuilder.Item;
        if (Backbone.Instance?.CellName is null)
            cellInstanceName = string.Empty;
        else
            cellInstanceName = Backbone.Instance.CellName;
        string cellInstanceConnectionName = equipmentElement.Name;
        FileConnectorConfiguration fileConnectorConfiguration = Mapper.Map(Configuration);
        string parameterizedModelObjectDefinitionType = methodBase.DeclaringType.FullName;
        IList<ModelObjectParameterDefinition> modelObjectParameters = Mapper.Map(ConfiguredParameters);
        _FileRead = CellInstanceConnectionName.Get(this, FileParameter, cellInstanceName, cellInstanceConnectionName, fileConnectorConfiguration, equipmentTypeName, parameterizedModelObjectDefinitionType, modelObjectParameters, equipmentDictionaryName, _DummyRuns, _StaticRuns, _UseCyclicalForDescription, connectionCount: null);
        if (_FileRead.IsEvent)
            AddParameterRangeAndEvent();
    }

    private object GetFilePathGeneratorInfo(string reportFullPath, bool isErrorFile)
    {
        FilePathGeneratorInfo result;
        FilePathGenerator filePathGeneratorOriginal;
        if (Configuration is null)
            result = null;
        else
        {
            if (isErrorFile)
            {
                if (_FilePathGeneratorForError is null)
                    _FilePathGeneratorForError = new FilePathGenerator(Configuration, reportFullPath, isErrorFile: true);
                filePathGeneratorOriginal = _FilePathGeneratorForError;
            }
            else
            {
                if (_FilePathGeneratorForTarget is null)
                    _FilePathGeneratorForTarget = new FilePathGenerator(Configuration, reportFullPath, isErrorFile: false);
                filePathGeneratorOriginal = _FilePathGeneratorForTarget;
            }
            result = new FilePathGeneratorInfo(filePathGeneratorOriginal, reportFullPath, isErrorFile, FileParameter);
        }
        return result;
    }

    private void FilePathGeneratorInfoMove(Tuple<string, Test[], JsonElement[], List<FileInfo>> extractResults, Exception exception = null)
    {
        bool isErrorFile = exception is not null;
        object filePathGeneratorInfo = GetFilePathGeneratorInfo(_FileRead.ReportFullPath, isErrorFile);
        if (filePathGeneratorInfo is not null and FilePathGeneratorInfo filePathGenerator)
        {
            string[] exceptionLines = _FileRead.Move(extractResults, filePathGenerator.To, filePathGenerator.From, filePathGenerator.ResolvedFileLocation, exception);
            if (isErrorFile)
            {
                try
                {
                    StringBuilder stringBuilder = new();
                    foreach (string item in exceptionLines)
                        _ = stringBuilder.Append("<").Append(item).AppendLine(">");
                    ISmtp smtp = Backbone.Instance.GetBackboneComponentsOfType<ISmtp>().SingleOrDefault();
                    EmailMessage emailMessage = new(_FileRead.ExceptionSubject, stringBuilder.ToString(), MailPriority.High);
                    smtp.Send(emailMessage);
                }
                catch (Exception) { }
            }
        }
    }

    private List<ParameterValue> GetParameterValues(List<string> headerNames, JsonElement[] jsonElements, int i)
    {
        List<ParameterValue> results = new();
        if (_UseCyclicalForDescription && i > 0)
            throw new Exception();
        object value;
        List<object[]> list;
        JsonProperty[] jsonProperties = jsonElements[i].EnumerateObject().ToArray();
        if (jsonProperties.Length != _EquipmentParameters.Count)
            throw new Exception();
        for (int p = 0; p < jsonProperties.Length; p++)
        {
            if (!_UseCyclicalForDescription || headerNames.Contains(jsonProperties[p].Name))
                value = jsonProperties[p].Value.ToString();
            else
            {
                list = new List<object[]>();
                for (int z = 0; z < jsonElements.Length; z++)
                    list.Add(new object[] { z, jsonElements[z].GetProperty(jsonProperties[p].Name).ToString() });
                value = list;
            }
            results.Add(new ParameterValue(_EquipmentParameters[p], value, DateTime.Now));
        }
        return results;
    }

    private void TriggerEvents(Tuple<string, Test[], JsonElement[], List<FileInfo>> extractResults)
    {
        List<string> headerNames;
        List<ParameterValue> parameters;
        if (!_UseCyclicalForDescription)
            headerNames = null;
        else
            headerNames = _FileRead.GetHeaderNames();
        for (int i = 0; i < extractResults.Item3.Length; i++)
        {
            if (_EquipmentEvent is not null)
            {
                _Log.Debug(string.Concat("TriggerEvent - {", _FileRead.ReportFullPath, "} ", i, " of ", extractResults.Item3.Length));
                parameters = GetParameterValues(headerNames, extractResults.Item3, i);
                if (_EquipmentEvent is not null)
                    Equipment.DataCollection.TriggerEvent(_EquipmentEvent, parameters);
                if (_UseCyclicalForDescription)
                    break;
            }
        }
    }

    public Tuple<string, Test[], JsonElement[], List<FileInfo>> ReExtract(string json)
    {
        Tuple<string, Test[], JsonElement[], List<FileInfo>> results;
        string cellInstanceName;
        if (!json.Contains(nameof(cellInstanceName)))
            throw new Exception();
        string equipmentTypeName;
        if (!json.Contains(nameof(equipmentTypeName)))
            throw new Exception();
        string equipmentDictionaryName;
        if (!json.Contains(nameof(equipmentDictionaryName)))
            throw new Exception();
        string cellInstanceConnectionName;
        if (!json.Contains(nameof(cellInstanceConnectionName)))
            throw new Exception();
        if (!json.Contains(nameof(FileConnectorConfiguration)))
            throw new Exception();
        if (!json.Contains(nameof(IList<ModelObjectParameterDefinition>)))
            throw new Exception();
        string parameterizedModelObjectDefinitionType;
        if (!json.Contains(nameof(parameterizedModelObjectDefinitionType)))
            throw new Exception();
        Dictionary<string, List<long>> dummyRuns = new();
        Dictionary<string, string> fileParameter = new();
        MethodBase methodBase = new StackFrame().GetMethod();
        JsonElement jsonElement = JsonSerializer.Deserialize<JsonElement>(json);
        cellInstanceName = jsonElement.GetProperty(nameof(cellInstanceName)).ToString();
        equipmentTypeName = jsonElement.GetProperty(nameof(equipmentTypeName)).ToString();
        equipmentDictionaryName = jsonElement.GetProperty(nameof(equipmentDictionaryName)).ToString();
        cellInstanceConnectionName = jsonElement.GetProperty(nameof(cellInstanceConnectionName)).ToString();
        JsonElement fileConnectorConfigurationJsonElement = jsonElement.GetProperty(nameof(FileConnectorConfiguration));
        parameterizedModelObjectDefinitionType = jsonElement.GetProperty(nameof(parameterizedModelObjectDefinitionType)).ToString();
        if (fileConnectorConfigurationJsonElement.ValueKind != JsonValueKind.Object)
            throw new Exception();
        JsonSerializerOptions jsonSerializerOptions = new() { Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) } };
        FileConnectorConfiguration fileConnectorConfiguration = JsonSerializer.Deserialize<FileConnectorConfiguration>(fileConnectorConfigurationJsonElement.ToString(), jsonSerializerOptions);
        JsonElement modelObjectParameterDefinitionJsonElement = jsonElement.GetProperty(nameof(IList<ModelObjectParameterDefinition>));
        if (modelObjectParameterDefinitionJsonElement.ValueKind != JsonValueKind.Array)
            throw new Exception();
        IList<ModelObjectParameterDefinition> modelObjectParameters = JsonSerializer.Deserialize<IList<ModelObjectParameterDefinition>>(modelObjectParameterDefinitionJsonElement.ToString(), jsonSerializerOptions);
        _FileRead = CellInstanceConnectionName.Get(this, FileParameter, cellInstanceName, cellInstanceConnectionName, fileConnectorConfiguration, equipmentTypeName, parameterizedModelObjectDefinitionType, modelObjectParameters, equipmentDictionaryName, _DummyRuns, _StaticRuns, _UseCyclicalForDescription, connectionCount: null);
        results = _FileRead.ReExtract();
        if (results?.Item2 is null)
            throw new Exception();
        else
        {
            TriggerEvents(results);
            _FileRead.Move(results);
            FilePathGeneratorInfoMove(results);
        }
        return results;
    }

}
// 2022-02-15 -> FileRead