using EgwProxy.SqlDb.Controllers;
using EgwProxy.SqlDb.DbModels;
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.Linq;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
using static IOB_UT_NEXT.CustomObj;

namespace IOB_WIN_SQL.IobSql
{
    /// <summary>
    /// Adapter specializzato per LANTEK e le chiamate tramite DB per i dati
    /// - PODL (ordini MES --&gt; MACCHINA)
    /// - ProdLog (da NestingDaLantek + RigheNestingDaLantek)
    /// </summary>

    public class SqlServLantek : Iob.GenericNext
    {
        #region Public Constructors

        /// <summary>
        /// Costruttore dell'IOB DB LANTEK
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IOBConf">Configurazione (legacy)</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public SqlServLantek(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            DateTime adesso = DateTime.Now;
            string SyncStateServ = getOptPar("SyncStateServer");
            string SyncStateDb = getOptPar("SyncStateDb");
            string SyncStateUser = getOptPar("SyncStateUser");
            string SyncStatePwd = getOptPar("SyncStatePwd");
            string SyncStateCTout = getOptPar("SyncStateCTout");
            string VetoReadSec = getOptPar("VetoReadSec");
            string VetoSyncSec = getOptPar("VetoSyncSec");
            string sFullPodlUrl = getOptPar("FullPodlUrl");

            setupSpecialParams();

            string connSyncState = $"data source={SyncStateServ};initial catalog={SyncStateDb};persist security info=True;user id={SyncStateUser};password={SyncStatePwd};MultipleActiveResultSets=True;App=IOB-WIN-NEXT";

            // gestione command timeout da https://erikej.github.io/sqlclient/2020/10/26/sqlclient-commandtimeout-preview.html
            if (!string.IsNullOrEmpty(SyncStateCTout))
            {
                connSyncState = $"{connSyncState};Command Timeout={SyncStateCTout}";
            }
            if (!string.IsNullOrEmpty(VetoReadSec))
            {
                int.TryParse(VetoReadSec, out vetoReadDbSec);
            }
            if (!string.IsNullOrEmpty(VetoSyncSec))
            {
                int.TryParse(VetoSyncSec, out VetoSyncDbSec);
            }
            if (!string.IsNullOrEmpty(sFullPodlUrl))
            {
                bool.TryParse(sFullPodlUrl, out FullPodlUrl);
            }

            // avvio DB controller
            dbProxy = new DbController(connSyncState);

            lastPING = DateTime.Now.AddHours(-1);
        }

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Implementazione custom esecuzione task specifici
        /// </summary>
        /// <param name="task2exe"></param>
        /// <returns></returns>
        public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe, string codTav)
        {
            DateTime adesso = DateTime.Now;
            // unico task ammissibile: fare un SYNC forzato...
            Dictionary<string, string> taskDone = new Dictionary<string, string>();
            if (task2exe != null)
            {
                // controllo se memMap != null...
                if (memMap != null)
                {
                    bool taskOk = false;
                    string taskVal = "";
                    // cerco task specifici: se ho startSetup --> imposto bit DBB701.DBB0.4
                    foreach (var item in task2exe)
                    {
                        taskOk = false;
                        taskVal = "";
                        // converto richiesta in enum...
                        taskType tName = taskType.nihil;
                        Enum.TryParse(item.Key, out tName);
                        // controllo sulla KEY...
                        switch (tName)
                        {
                            case taskType.setComm:
                            case taskType.syncDbData:
                                lgInfo($"executeTasks --> {tName}");
                                // per prima cosa recupero elenco PODL attivi x la macchina
                                List<PODLModel> listaPODL = new List<PODLModel>();
                                bool okPodl = false;
                                string rawListPODL = "";
                                try
                                {
                                    // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
                                    Task.Run(async () =>
                                    {
                                         rawListPODL = await callUrl(urlGetNextPODL, false);
                                    })
                                    .GetAwaiter()
                                    .GetResult();
                                }
                                catch (Exception ex)
                                {
                                    lgError($"Errore in callUrl per {urlGetNextPODL}: {ex.Message}");
                                }
                                if (!string.IsNullOrEmpty(rawListPODL))
                                {
                                    try
                                    {
                                        listaPODL = JsonConvert.DeserializeObject<List<PODLModel>>(rawListPODL);
                                        okPodl = listaPODL.Count > 0;
                                    }
                                    catch (Exception exc)
                                    {
                                        lg.Error($"Errore: chiamata elenco PODL ha restituito errore{Environment.NewLine}{exc}");
                                    }
                                }
                                else
                                {
                                    lg.Error($"Errore: chiamata elenco PODL ({urlGetNextPODL}) ha restituito valore vuoto");
                                }
                                if (okPodl)
                                {
                                    //mando elenco PODL...
                                    List<MesPODLReqModel> CurrPodlReq = listaPODL
                                                                    .Select(x => new MesPODLReqModel()
                                                                    {
                                                                        Attivabile = x.Attivabile,
                                                                        CodArticolo = x.CodArticolo,
                                                                        CodCli = x.CodCli,
                                                                        CodGruppo = x.CodGruppo,
                                                                        DueDate = x.DueDate ?? DateTime.Now,
                                                                        IdxMacchina = x.IdxMacchina,
                                                                        IdxODL = x.IdxOdl,
                                                                        IdxPromessa = x.IdxPromessa,
                                                                        InsertDate = x.InsertDate,
                                                                        KeyBCode = x.KeyBCode,
                                                                        KeyRichiesta = x.KeyRichiesta,
                                                                        Note = x.Note,
                                                                        NumPezzi = x.NumPezzi,
                                                                        Priorita = x.Priorita,
                                                                        PzPallet = x.PzPallet,
                                                                        TCAssegnato = x.Tcassegnato
                                                                    })
                                                                    .ToList();
                                    // scrivo i record richiesti
                                    bool fatto = dbProxy.MesPodlWriteReq(CurrPodlReq);
                                    if (fatto)
                                    {
                                        taskVal = $"EXECUTED task: {item.Key} / {item.Value}";
                                        // registro evento chiamata scrittura in syncState...
                                        string tabName = item.Key;
                                        var currSyncState = elencoSyncState.FirstOrDefault(x => x.TableName == tabName);
                                        // verifico x registrare azione
                                        if (currSyncState == null)
                                        {
                                            currSyncState = new SyncStateModel()
                                            {
                                                LastIdx = 0,
                                                LastUpdate = adesso,
                                                TableName = tabName,
                                                Note = "Init"
                                            };
                                        }
                                        else
                                        {
                                            var lastRec = CurrPodlReq.OrderBy(x => x.IdxPromessa).LastOrDefault();
                                            currSyncState.LastIdx = lastRec != null ? lastRec.IdxPromessa : -1;
                                            currSyncState.Note = $"Esecuzione {tabName} per {CurrPodlReq.Count} rec | last code: {currSyncState.LastIdx}";
                                            currSyncState.LastUpdate = adesso;
                                        }
                                        // salvo sync...
                                        dbProxy.SyncStateUpsert(currSyncState);
                                    }

                                    // effettua sync scrivendo i dati in export (PODL)
                                    execExportAll();
                                }

                                break;

                            default:
                                taskVal = $"IobSqlServLantek | taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
                                lgInfo($"IobSqlServLantek | chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
                                break;
                        }
                        // aggiungo task!
                        taskDone.Add(item.Key, taskVal);
                    }
                }
            }
            lastReadPLC = DateTime.Now;
            return taskDone;
        }

        /// <summary>
        /// Recupero dati dinamici... FAKE!
        /// </summary>
        public override Dictionary<string, string> getDynData()
        {
            DateTime adesso = DateTime.Now;

            // dizionario vuoto / gestito da altro adapter file-based
            Dictionary<string, string> outVal = new Dictionary<string, string>();

            lastReadPLC = DateTime.Now;
            return outVal;
        }

        /// <summary>
        /// Effettua processing CUSTOM:
        /// - esegue tutti gli import
        /// </summary>
        public override void processCustomTaskLF()
        {
            lgInfo($"Richiesto processCustomTaskLF");
            // effettua sync
            execExportAll();
            execImportAll();
            lastReadPLC = DateTime.Now;
        }

        /// <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;
            if (connectionOk)
            {
                // controllo veto checkDB
                if (adesso > vetoDataRead)
                {
                    // predispongo prox veto...
                    vetoDataRead = adesso.AddSeconds(vetoReadDbSec);
                    if (adesso > vetoDataSync)
                    {
                        // effettua sync
                        execImportAll();
                        vetoDataSync = adesso.AddSeconds(VetoSyncDbSec);
                    }

                    // semaforo
                    currDispData.semIn = Semaforo.SV;
                    // recupero syncState
                    elencoSyncState = dbProxy.SyncStateGetAll();
                    // verifico ProdData e processo
                    bool sentProdData = processProdDataTable(adesso);
                }
            }
            else
            {
                B_input = 0;
                currDispData.semIn = Semaforo.SR;
            }
        }

        /// <summary>
        /// Override connessione
        /// </summary>
        public override void tryConnect()
        {
            if (!connectionOk)
            {
                // controllo che il ping sia stato tentato almeno pingTestSec fa...
                if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec"))
                {
                    if (verboseLog || periodicLog)
                    {
                        lgInfo("SqlDb LANTEK: ConnKO - tryConnect");
                    }
                    // in primis salvo data ping...
                    lastPING = DateTime.Now;
                    // se passa il ping faccio il resto...
                    if (testPingMachine == IPStatus.Success)
                    {
                        string szStatusConnection = "";
                        try
                        {
                            // ora provo connessione...
                            parentForm.commPlcActive = true;
                            if (dbProxy != null)
                            {
                                elencoSyncState = dbProxy.SyncStateDoImportAll();

                                if (elencoSyncState != null && elencoSyncState.Count > 0)
                                {
                                    parentForm.commPlcActive = false;
                                    connectionOk = true;
                                }
                            }
                            // refresh stato connessione!!!
                            if (connectionOk)
                            {
                                queueInEnabCurr = true;
                                if (adpRunning)
                                {
                                    lgInfo("Connessione OK");
                                    lastReadPLC = DateTime.Now;
                                }
                            }
                            else
                            {
                                lgError("Impossibile procedere, connessione mancante...");
                            }
                        }
                        catch (Exception exc)
                        {
                            lgFatal($"Errore nella connessione all'adapter SqlDb LANTEK: {szStatusConnection}{Environment.NewLine}{exc}");
                            connectionOk = false;
                            lgInfo($"Eccezione in TryConnect, Adapter SqlDb LANTEK NON running, pausa di {utils.CRI("waitRecMSec")} msec prima di ulteriori tentativi di riconnessione");
                        }
                    }
                    else
                    {
                        // loggo no risposta ping ...
                        connectionOk = false;
                        if (verboseLog || periodicLog)
                        {
                            lgInfo($"Attenzione: SqlDb LANTEK controllo PING fallito per IP {IOBConfFull.Device.Connect.PingIpAddr}");
                        }
                    }
                }
            }
            else
            {
                needRefresh = true;
            }
        }

        public override void tryDisconnect()
        {
            // registro solo che è disconnesso
            connectionOk = false;
            queueInEnabCurr = false;
        }

        #endregion Public Methods

        #region Protected Fields

        protected bool FullPodlUrl = false;
        protected int vetoReadDbSec = 3;

        protected int VetoSyncDbSec = 20;

        #endregion Protected Fields

        #region Protected Properties

        protected DbController dbProxy { get; set; } = null;

        /// <summary>
        /// Stato di sync delle tab gestite
        /// </summary>
        protected List<SyncStateModel> elencoSyncState { get; set; } = new List<SyncStateModel>();

        #endregion Protected Properties

        #region Private Methods

        /// <summary>
        /// Esegue task EXPORT (MES PODL to MACHINE)
        /// </summary>
        private void execExportAll()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            elencoSyncState = dbProxy.SyncStateDoExportAll();
            sw.Stop();
            DateTime adesso = DateTime.Now;
            lastReadPLC = adesso;
            lgInfo($"DB: esecuzione task dbProxy.SyncStateDoExportAll() in {sw.ElapsedMilliseconds} ms");

            if (elencoSyncState != null)
            {
                // registro evento chiamata scrittura in syncState...
                string tabName = "ExportAll";
                var currSyncState = elencoSyncState.FirstOrDefault(x => x.TableName == tabName);
                // verifico x registrare azione
                if (currSyncState == null)
                {
                    currSyncState = new SyncStateModel()
                    {
                        LastIdx = 0,
                        LastUpdate = adesso,
                        TableName = tabName,
                        Note = "Init"
                    };
                }
                else
                {
                    // calcolo lastIDX valore da datetime
                    int valInt = -1;
                    var nowString = $"{adesso:HHmmss}";
                    int.TryParse(nowString, out valInt);
                    currSyncState.LastIdx = valInt;
                    currSyncState.Note = $"Esecuzione {tabName} | DT code: {currSyncState.LastIdx}";
                    currSyncState.LastUpdate = adesso;
                }
                // salvo sync...
                dbProxy.SyncStateUpsert(currSyncState);
                foreach (var item in elencoSyncState)
                {
                    lgTrace($"TAB {item.TableName} | LastIdx {item.LastIdx} | Note {item.Note} | Last Upd {item.LastUpdate}");
                }
            }
        }

        /// <summary>
        /// Esegue task IMPORT (MES PODL to MACHINE)
        /// </summary>
        private void execImportAll()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            elencoSyncState = dbProxy.SyncStateDoImportAll();
            sw.Stop();
            lastReadPLC = DateTime.Now;
            lgInfo($"DB: esecuzione task dbProxy.SyncStateDoImportAll() in {sw.ElapsedMilliseconds} ms");

            if (elencoSyncState != null)
            {
                foreach (var item in elencoSyncState)
                {
                    lgTrace($"TAB {item.TableName} | LastIdx {item.LastIdx} | Note {item.Note} | Last Upd {item.LastUpdate}");
                }
            }
        }

        /// <summary>
        /// Esegue processing + invio dati tab ProdData
        /// </summary>
        /// <param name="adesso"></param>
        /// <returns></returns>
        private bool processProdDataTable(DateTime adesso)
        {
            bool fatto = false;
            string tabNameIn = "ProdData";
            string tabNameOut = "ProdDataToMes";
            // cerco info sui SignLog (ToMes e letti)
            var currSignLogRead = elencoSyncState.FirstOrDefault(x => x.TableName == tabNameIn);
            // se nullo inizializzo
            if (currSignLogRead == null)
            {
                currSignLogRead = new SyncStateModel()
                {
                    LastIdx = 0,
                    LastUpdate = adesso,
                    TableName = tabNameIn,
                    Note = "Init"
                };
            }
            var currSignLogSent = elencoSyncState.FirstOrDefault(x => x.TableName == tabNameOut);
            // se nullo inizializzo
            if (currSignLogSent == null)
            {
                currSignLogSent = new SyncStateModel()
                {
                    LastIdx = 0,
                    LastUpdate = adesso,
                    TableName = tabNameOut,
                    Note = "Init"
                };
            }
            // verifica se ci siano dati da trasmettere (sui valori LastIdx)
            if (currSignLogRead.LastIdx > currSignLogSent.LastIdx)
            {
                // recupero i dati dal DB...
                var data2send = dbProxy.MachProdDataGetNew(currSignLogSent.LastIdx);
                // se ho dati preparo invio
                if (data2send != null && data2send.Count > 0)
                {
                    foreach (var sLog2send in data2send)
                    {
                        if (sLog2send.Action == "StartOrd")
                        {
                            try
                            {
                                // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
                                Task.Run(async () =>
                                {
                                    if (FullPodlUrl)
                                    {
                                        // se il record è apertura PODL chiamo apertura
                                        await TrySetupPODLAsync(sLog2send.IdxPodl, true, sLog2send.DtEve, DateTime.Now);
                                    }
                                    else
                                    {
                                        await TrySetupPODLAsync(0);
                                    }
                                })
                                .GetAwaiter()
                                .GetResult();
                            }
                            catch (Exception ex)
                            {
                                lgError($"Errore in tryClosePODL | {Environment.NewLine}{ex.Message}");
                            }
                        }
                        else if (sLog2send.Action == "EndOrd")
                        {
                            // se il record è chiusura PODL chiamo chiusura... DOPO aver cercato
                            // PODL --> ODL
                            if (FullPodlUrl)
                            {
                                TryClosePODL(sLog2send.IdxPodl, sLog2send.DtEve, DateTime.Now);
                            }
                            else
                            {
                                TryClosePODL(0);
                            }
                        }
                    }
                }

                // aggiorno idx inviato...
                currSignLogSent.Note = $"Updated from {currSignLogRead.LastIdx}";
                currSignLogSent.LastUpdate = adesso;
                currSignLogSent.LastIdx = currSignLogRead.LastIdx;
                fatto = true;
            }

            // alla fine aggiorno i dati inviati!
            dbProxy.SyncStateUpsert(currSignLogSent);

            return fatto;
        }

        #endregion Private Methods
    }
}