using EgwProxy.Shelly;
using EgwProxy.Shelly.Clients;
using EgwProxy.Shelly.Options;
using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.NetworkInformation;
using System.Threading;
using System.Threading.Tasks;

namespace IOB_WIN_SHELLY.Iob
{
    /// <summary>
    /// Classe gestione oggetti Shelly tipo Gen2 (Pro3Em)
    /// </summary>
    public class ShellyClientGen2 : GenericNext
    {
        #region Public Constructors

        /// <summary>
        /// Estende l'init della classe base, impiegando il pacchetto EgwProxy.Shelly
        /// - gestione dei task da svolgere da configurazione json specifica
        /// - specializzazione da conf e non da codice
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public ShellyClientGen2(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            lgInfo("Init Iob.ShellyClientGen2");
            // imposto ping a 3
            maxQueuePing = 3;
            // imposto
            B_input = 0;
            // init datetime counters
            DateTime adesso = DateTime.Now;
            lastPzCountSend = adesso;
            lastWarnODL = adesso;
            vetoCheckStatus = adesso;
            // 2023.09.05 imposto anche primo ping e check disconnected...
            lastPING = adesso;
            lastDisconnCheck = adesso;
            var VETO_PING_SEC = getOptPar("VETO_PING_SEC");
            if (!string.IsNullOrEmpty(VETO_PING_SEC))
            {
                int.TryParse(VETO_PING_SEC, out vetoPingSec);
            }
            var POWEROFF_TIMEOUT_SEC = getOptPar("POWEROFF_TIMEOUT_SEC");
            if (!string.IsNullOrEmpty(POWEROFF_TIMEOUT_SEC))
            {
                int.TryParse(POWEROFF_TIMEOUT_SEC, out PoweroffTimeoutSec);
            }

            var devData = IobConfFull.Device.Connect;
            ShellyOptions options = new ShellyOptions()
            {
                DefaultTimeout = TimeSpan.FromMilliseconds(devData.PingMsTimeout * 10),
                ServerUri = new Uri($"http://{devData.IpAddr}/rpc")
            };
            // init secondo il tipo di Shelly...

            shellyCli = new ShellyPro3EmClient(new HttpClient(), options);

        }

        public override async Task InitializeAsync()
        {
            // invio conf macchina all'inizio
            await SendMachineConfAsync();
        }

        #endregion Public Constructors

        #region Public Methods

        public override Dictionary<string, string> getDynData()
        {
            Dictionary<string, string> outData = new Dictionary<string, string>();
            // verifico che non sia una chiamata troppo frequente, ogni 10 sec minimo...
            if (DateTime.Now > lastReadPLC.AddSeconds(shellyCallDelay))
            {
                // chiama lettura status valor RT (Potenza, Corrente...)
                lastShellyRespRT = Task.Run(() => ((ShellyPro3EmClient)shellyCli).GetEmStatus(CancellationToken.None, 0)).Result;
                lastShellyRespEnergy = Task.Run(() => ((ShellyPro3EmClient)shellyCli).GetEmDataStatus(CancellationToken.None, 0)).Result;

                // Salvo negli oggetti DynData x EnergyTotal
                if (lastShellyRespEnergy.IsSuccess)
                {
                    lastReadPLC = DateTime.Now;
                    // recupero da conf memMap e ciclo
                    foreach (var item in IOBConfFull.Memory.mMapRead)
                    {
                        //string outVal = "";
                        double outValDbl = 0;
                        // se trovo includo in dynData...
                        switch (item.Value.memAddr)
                        {
                            case "ActEnergy":
                                outValDbl = lastShellyRespEnergy.Value.TotalAll.ActEnergy;
                                break;

                            case "ActEnergy_PhaseA":
                                outValDbl = lastShellyRespEnergy.Value.TotalPhaseA.ActEnergy;
                                break;

                            case "ActEnergy_PhaseB":
                                outValDbl = lastShellyRespEnergy.Value.TotalPhaseB.ActEnergy;
                                break;

                            case "ActEnergy_PhaseC":
                                outValDbl = lastShellyRespEnergy.Value.TotalPhaseC.ActEnergy;
                                break;

                            default:
                                break;
                        }
                        // se ho fattore --> gestisco!
                        if (item.Value.factor != 1)
                        {
                            outValDbl = outValDbl * item.Value.factor;
                        }
                        // modalità saveValue x reduce fLog
                        saveValue(ref outData, item.Key, outValDbl);
                    }
                }

                // Salvo negli oggetti DynData x RT
                if (lastShellyRespRT.IsSuccess)
                {
                    lastReadPLC = DateTime.Now;
                    // recupero da conf memMap e ciclo
                    foreach (var item in IOBConfFull.Memory.mMapRead)
                    {
                        //string outVal = "";
                        double outValDbl = 0;
                        // se trovo includo in dynData...
                        switch (item.Value.memAddr)
                        {

                            case "Current":
                                outValDbl = lastShellyRespRT.Value.TotalCurrent;
                                break;
                            case "Current_Neutral":
                                outValDbl = lastShellyRespRT.Value.NeutralCurrent;
                                break;
                            case "Current_PhaseA":
                                outValDbl = lastShellyRespRT.Value.PhaseA.Current;
                                break;
                            case "Current_PhaseB":
                                outValDbl = lastShellyRespRT.Value.PhaseB.Current;
                                break;
                            case "Current_PhaseC":
                                outValDbl = lastShellyRespRT.Value.PhaseC.Current;
                                break;

                            case "Freq_PhaseA":
                                outValDbl = lastShellyRespRT.Value.PhaseA.Frequency;
                                break;
                            case "Freq_PhaseB":
                                outValDbl = lastShellyRespRT.Value.PhaseB.Frequency;
                                break;
                            case "Freq_PhaseC":
                                outValDbl = lastShellyRespRT.Value.PhaseC.Frequency;
                                break;

                            case "Power":
                                outValDbl = lastShellyRespRT.Value.TotalActivePower;
                                break;
                            case "Power_PhaseA":
                                outValDbl = lastShellyRespRT.Value.PhaseA.ActivePower;
                                break;
                            case "Power_PhaseB":
                                outValDbl = lastShellyRespRT.Value.PhaseB.ActivePower;
                                break;
                            case "Power_PhaseC":
                                outValDbl = lastShellyRespRT.Value.PhaseC.ActivePower;
                                break;

                            case "Voltage_PhaseA":
                                outValDbl = lastShellyRespRT.Value.PhaseA.Voltage;
                                break;
                            case "Voltage_PhaseB":
                                outValDbl = lastShellyRespRT.Value.PhaseB.Voltage;
                                break;
                            case "Voltage_PhaseC":
                                outValDbl = lastShellyRespRT.Value.PhaseC.Voltage;
                                break;

                            default:
                                break;
                        }
                        // se ho fattore --> gestisco!
                        if (item.Value.factor != 1)
                        {
                            outValDbl = outValDbl * item.Value.factor;
                        }
                        // modalità saveValue x reduce fLog
                        saveValue(ref outData, item.Key, outValDbl);
                    }
                }
                // traccio dati ricevuti
                trackExchDataRaw(lastShellyRespRT, 512);
                // traccio dati ricevuti
                trackExchDataRaw(lastShellyRespEnergy, 256);
            }
            return outData;
        }

        /// <summary>
        /// Effettua lettura semafori principale <paramref name="currDispData">Parametri da
        /// aggiornare x display in form</paramref>
        /// </summary>
        public override void readSemafori(ref newDisplayData currDispData)
        {
            DateTime adesso = DateTime.Now;
            // se ha risposto ad ultima chiamata --> ok
            if (connectionOk && adesso.Subtract(lastReadPLC).TotalMinutes < 1)
            {
                B_input = 3;
                // aggiungo NON emergenza...
                B_input += (1 << 7);
            }
            else
            {
                B_input = 0;
            }
            lastReadPLC = DateTime.Now;
            lastWatchDog = adesso;
        }

        public override void startAdapter(bool resetQueue)
        {
            base.startAdapter(resetQueue);
            // 2023.09.05 imposto anche primo ping e check disconnected...
            DateTime adesso = DateTime.Now;
            lastWatchDog = adesso;
            //lastPING = adesso;
            lastReadPLC = adesso;
            lastDisconnCheck = adesso;
            // faccio un primo check POST ritardo
            tryConnect();
        }

        /// <summary>
        /// Override connessione
        /// </summary>
        public override void tryConnect()
        {
            bool doLog = (verboseLog || periodicLog);
            lgDebug($"Shelly: tryConnect step 01 | connectionOk: {connectionOk}");
            if (!connectionOk)
            {
                //// resetto coda...
                //QueuePing = new DataQueue("000", "QueuePing", false);
                // controllo che il ping sia stato tentato almeno pingTestSec fa...
                if (DateTime.Now.Subtract(lastPING).TotalSeconds > vetoPingSec)
                {
                    if (doLog)
                    {
                        lgInfo("Shelly: ConnKO - tryConnect");
                    }
                    lgDebug("Shelly: tryConnect step 04");

                    lgDebug("Shelly: Reset QueuePing");

                    bool pingOK = testPingMachine == IPStatus.Success;
                    addTest(pingOK);

                    // se passa il ping faccio il resto...
                    if (pingStatusOk() || pingOK)
                    {
                        // in primis salvo data ping...
                        lastPING = DateTime.Now;
                        connectionOk = true;
                        queueInEnabCurr = true;
                        lgInfo("Shelly - ping OK");
                    }
                    else
                    {
                        // loggo no risposta ping ...
                        lgError("Shelly - ping KO");
                    }
                }
            }
        }

        /// <summary>
        /// Override disconnessione
        /// </summary>
        public override void tryDisconnect()
        {
            lgInfo("Richiesta disconnessione adapter Shelly!");
            connectionOk = false;
            queueInEnabCurr = false;
        }

        #endregion Public Methods

        #region Private Fields

        /// <summary>
        /// Oggetto dove viene salvata ultima risposta Shelly x dati RT (potenza, correnti, ...)
        /// </summary>
        private ShellyResult<EgwProxy.Shelly.DTO.Gen2.EMDto> lastShellyRespRT;

        /// <summary>
        /// Oggetto dove viene salvata ultima risposta Shelly x dati Energia (Total per fase e globle)
        /// </summary>
        private ShellyResult<EgwProxy.Shelly.DTO.Gen2.EMDataDto> lastShellyRespEnergy;

        private int PoweroffTimeoutSec = 100;

        /// <summary>
        /// Delay tra chiamate in sec
        /// </summary>
        private int shellyCallDelay = 1;

        /// <summary>
        /// Client interno x comunicazione con Shelly devices
        /// </summary>
        private ShellyClientBase shellyCli;

        /// <summary>
        /// Veto controllo status x log...
        /// </summary>
        private DateTime vetoCheckStatus = DateTime.Now;

        #endregion Private Fields

        #region Private Methods

        private void addTest(bool pingOk)
        {
            int score = pingOk ? 1 : 0;
            // controllo: se era spenta e risulta ping ok --> reset coda!
            if (B_input == 0 && pingOk)
            {
                B_input = 1;
                QueuePing = new DataQueue(IOBConfFull.General.FilenameIOB, "QueuePing", false, redisMan);
                lgTrace($"QueuePing resetted on addTest");
            }
            QueuePing.Enqueue($"{score}");
            while (QueuePing.Count > maxQueuePing)
            {
                string res = "";
                QueuePing.TryDequeue(out res);
            }
        }

        /// <summary>
        /// Calcola status ping:
        /// - se ha ‹ 50% coda richiesta --› true
        /// - se ha › 50% coda richiesta --› true se è maggior parte a 1 (true)
        /// </summary>
        /// <returns></returns>
        private bool pingStatusOk()
        {
            bool answ = false;
            long numVal = QueuePing.Count;
            if (numVal > maxQueuePing / 2)
            {
                var listaValori = QueuePing.ToList();
                long numOk = listaValori.Where(x => x == "1").Count();
                long numKo = numVal - numOk;
                answ = numOk >= numKo;
                lgTrace($"PING ok per: {numOk} > {numKo}");
            }
            else
            {
                lgTrace($"PING check: {answ} per mancanza dati minimi test");
            }
            return answ;
        }

        #endregion Private Methods
    }
}