using FSI.BT.IR.Plc.Settings.Log.Data;
using FSI.BT.IR.Plc.Settings.Log.Models;
using FSI.BT.IR.Plc.Settings.Log.Settings;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using NLog;
using Sharp7;
using System.Data.Common;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Security.AccessControl;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Xml.Linq;
using static FSI.BT.IR.Plc.Settings.Log.Settings.Context;

namespace FSI.BT.IR.Plc.Settings.Log
{
    public class Worker : BackgroundService
    {
        private Logger _log = LogManager.GetCurrentClassLogger();

        private ISettings _settings;
        private IServiceProvider _serviceProvider;
        private ApplicationDbContext _context;

        public Worker(ISettings settings, IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
            _settings = settings;
        }

        /// <summary>
        /// Standard Taks
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {

            using var scope = _serviceProvider.CreateScope();
            _context = scope.ServiceProvider.GetService<ApplicationDbContext>();

            DateTime oldDate = DateTime.Now;

            var plc = new S7Client(); // SPS-Verbindung
            List<Models.Parameter> parameters;
            List<Models.Logging> loggs;
            byte[] buffer;
            string value = string.Empty;
            Models.Logging logFirstEntry;

            parameters = _context.Parameters.Include(x => x.Plc).ToList(); // Parameter auslesen
            loggs = _context.Logging.Include(x => x.Parameter).OrderByDescending(x => x.Timestamp).ToList(); // Logging auslesen

            while (!stoppingToken.IsCancellationRequested)
            {
                if (oldDate.Date != DateTime.Now.Date && _settings.Cfg.CleanInterval > 0 ) 
                {
                    _log.Debug("Neuer Tag.");                    
                    CleanLoggTable();
                }

                parameters = _context.Parameters.ToList();

                foreach (var parameter in parameters)
                {
                    value = string.Empty;
                    logFirstEntry = new();
                    buffer = new byte[parameter.DbSize];

                    try
                    {
                        // Verbindung mit SPS-Aufbauen
                        var connectionRslt = plc.ConnectTo(parameter.Plc.IpAdress, parameter.Plc.Rack, parameter.Plc.Slot);

                        // Verbindungsstatus überprüfen
                        if (connectionRslt == 0) // Verbindung i.O.
                        {
                            _log.Debug(parameter.Plc.Name + " Verbindung hergestellt.");
                        }
                        else // Verbindung n.i.O.
                        {
                            _log.Error(parameter.Plc.Name + " Verbindung nicht hergestellt.");
                            _log.Error(parameter.Plc.Name + " Fehler: " + plc.ErrorText(connectionRslt));
                            // await Task.Delay(UpdateIntervall, cancellationToken); // Warten bis zum nächsten Verbindungsversuch (Zeiten aus config.json)
                            continue;
                        }
                    }
                    catch (Exception ex)
                    {
                        _log.Error(parameter.Plc.Name + " " + ex.Message);
                    }

                    plc.DBRead(parameter.DbNo, parameter.DbStart, parameter.DbSize, buffer); // Wert aus SPS auslesen
                  
                    plc.Disconnect(); // Verbindung zur SPS trennen
                    _log.Debug(parameter.Plc.Name + " Verbindung getrennt.");
                                      
                    value = GetStringOfValue(buffer, parameter); // Value in Sting, in Abhänigkeit des Datentyps, wandeln
                    _log.Debug(parameter.Name + ": Wert " + value + " wurde ausgelesen" +
                        "");
                    logFirstEntry = loggs.Where(x => x.ParameterId == parameter.Id).FirstOrDefault();


                    if (logFirstEntry == null || !value.Equals(logFirstEntry.Value))
                    {
                        Models.Logging logging = new Models.Logging();
                        logging.Value = value;
                        logging.Parameter = parameter;

                        _context.Logging.Add(logging);
                        _context.SaveChanges();

                        loggs = _context.Logging.OrderByDescending(x => x.Timestamp).ToList();
                    }

                    _log.Debug(parameter.Plc.Name + " Start Task-Wartezeit");
                    try
                    {
                        await Task.Delay(_settings.Cfg.UpdateInterval, stoppingToken); // Warten bis zum nächsten Verbindungsversuch (Zeiten aus config.json)
                    }
                    catch (Exception ex)
                    {
                        _log.Error(parameter.Plc.Name + " " + ex.Message);
                    }
                    _log.Debug(parameter.Plc.Name + " Ende Task-Wartezeit");
                }
            }
        }

        private void CleanLoggTable()
        {
            List<Models.Parameter> parameters = _context.Parameters.ToList();
            List<Models.Logging> loggs;
            int span;
            int cntDel = 0;

            _log.Debug("Datenbereinigung gestartet");

            foreach (var parameter in parameters) 
            {
                loggs = _context.Logging.Where(x => x.ParameterId == parameter.Id).OrderByDescending(x => x.Timestamp).ToList();

                if (loggs != null && loggs.Count > 1)
                {
                    for (int i = 1; i < loggs.Count; i++)
                    {
                        span = Math.Abs(loggs[i].Timestamp.Subtract(DateTime.Now).Days);
                        if (span > _settings.Cfg.CleanInterval)
                        {
                            _context.Logging.Remove(loggs[i]);
                            cntDel++;
                            _log.Debug("Logg mit der ID: " + loggs[i].Id + " wird gelöscht.");
                        }
                    }
                }
            }

            _context.SaveChanges();
            _log.Info("Datenbereinigung beendet - es wurden " + cntDel.ToString() +" Datensätze gelöscht");
        }

        private string GetStringOfValue(byte[] buffer, Models.Parameter parameter)
        {
            // Wert nach Datentyp konvertieren
            switch (parameter.DataType)
            {
                case DataType.Int: // Integer
                    return S7.GetIntAt(buffer, 0).ToString();

                case DataType.DInt: // double Integer
                    return S7.GetDIntAt(buffer, 0).ToString();

                case DataType.Real: // Real
                    return S7.GetRealAt(buffer, 0).ToString();

                default:
                    return string.Empty;

            }
        }
    }
}
