using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using Newtonsoft.Json;
using RestSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.NetworkInformation;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace IOB_WIN_WS.IobWs
{
    /// <summary>
    /// Adapter base per sviluppo chiamate con servizi REST
    /// </summary>
    public class RestBase : Iob.GenericNext
    {
        #region Public Constructors

        /// <summary>
        /// Costruttore dell'IOB Rest generico
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IOBConf">Configurazione (legacy)</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public RestBase(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            lgInfo($"Richiesto Adapter IobRest.Base con i parametri seguenti | ADDR: {IOBConfFull.Device.Connect.IpAddr} | PORT: {IOBConfFull.Device.Connect.Port}");
            lastPING = DateTime.Now.AddHours(-1);
            // predispongo configurazione specifica Rest...
            if (!string.IsNullOrEmpty(getOptPar("REST_CONF")))
            {
                setupRestConf(getOptPar("REST_CONF"));
            }
        }

        #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)
        {
            /*---------------------------------------
             * gestione execute task SPECIFICI x pesa:
             * - salva i parametri richiesta (RM, cod1..cod6)
             * - esegue metodo richiesta (IN/OUT)
             *---------------------------------------*/

            // Verificare il protocollo: dovrebbe togliere SOLO i task eseguiti...
            Dictionary<string, string> taskDone = new Dictionary<string, string>();
#if false
            if (task2exe != null)
            {
                lgTrace($"executeTasks: richiesta esecuzione {task2exe.Count} task");
                // controllo se memMap != null...
                if (memMap != null)
                {
                    bool taskOk = false;
                    string taskVal = "";
                    // cerco task specifici: qui sono NON standard...
                    foreach (var item in task2exe)
                    {
                        lgInfo($"TASK | {item.Key} --> {item.Value}");
                        taskOk = false;
                        taskVal = "";
                        // converto richiesta in enum...
                        taskType tName = taskType.nihil;
                        Enum.TryParse(item.Key, out tName);
                        // controllo sulla KEY...
                        switch (tName)
                        {
                            case taskType.setParameter:
                                // richiedo da URL i parametri WRITE da popolare
                                lgInfo("Chiamata setParameter --> processMemWriteRequests");
                                taskVal = processMemWriteRequests();
                                // se restituiscce "" faccio altra prova...
                                if (string.IsNullOrEmpty(taskVal))
                                {
                                    // i parametri me li aspetto come stringa composta paramName|paramvalue
                                    if (item.Value.Contains("|"))
                                    {
                                        string[] paramsJob = item.Value.Split('|');
                                        taskVal = $"REQUEST SET PARAMETERS: {paramsJob[0]} --> {paramsJob[1]}";
                                    }
                                    else
                                    {
                                        taskVal = $"WRONG REQUEST FOR SET PARAMETERS: {item.Value} doesnt contain pipe for splitting key/value";
                                    }
                                }
                                break;

                            default:
                                taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
                                lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
                                break;
                        }
                        // aggiungo task!
                        taskDone.Add(item.Key, taskVal);
                    }
                }
                else
                {
                    lgError($"Attenzione! memMap è nullo, non posso eseguire task2exe!");
                }
            }
#endif

            return taskDone;
        }

        /// <summary>
        /// Recupero dati dinamici...
        /// </summary>
        public override Dictionary<string, string> getDynData()
        {
            // valore non presente in vers default... se gestito fare override
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            // indico esecuzione e proseguo
            lastReadPLC = DateTime.Now;
            return outVal;
        }

        /// <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;
            lastReadPLC = adesso;
            // verifico non sia in veto invio iniziale...
            if (queueInEnabCurr)
            {
                try
                {
                    currDispData.semIn = Semaforo.SV;
                    // effettua refresh dati da leggere SPECIFICi x citizen...
                    refreshData();
                    // decodifica e gestione
                    decodeToBaseBitmap(ref currDispData);
                    // display
                    reportRawInput(ref currDispData);
                }
                catch (Exception exc)
                {
                    currDispData.semIn = Semaforo.SR;
                    lgError($"Eccezione in readSemafori:{Environment.NewLine}{exc}");
                }
            }
            else
            {
                lgDebug($"[VETO readSemafori] | veto attivo alle {adesso:yyyy.MM.dd HH:mm:ss}");
                checkVetoQueueIn();
            }
        }

        /// <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("Rest: 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;

                            // chiamo metodo connect
                            var checkResp = ExecuteCallGet(GetUrlResource("GetConnection"), true);
                            // forse va eliminato...
                            lgInfo($"GetConnection | {checkResp} (Base.tryConnect)");
                            if (checkResp != null && checkResp.ToLower().Contains("true"))
                            {
                                connectionOk = true;
                            }
                            else
                            {
                                lgError($"Errore check connessione | checkResp: {checkResp}");
                            }
                            // refresh stato connessione!!!
                            if (connectionOk)
                            {
                                queueInEnabCurr = true;
                                if (adpRunning)
                                {
                                    lgInfo("Connessione OK");
                                }
                            }
                            else
                            {
                                lgError("Impossibile procedere, connessione mancante...");
                            }
                        }
                        catch (Exception exc)
                        {
                            lgFatal($"Errore nella connessione all'Adapter IobRest.Base: {szStatusConnection}{Environment.NewLine}{exc}");
                            connectionOk = false;
                            lgInfo($"Eccezione in TryConnect, Adapter IobRest.Base 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: Rest 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

        /// <summary>
        /// Api Url di base x chiamate REST
        /// </summary>
        protected string apiUrl = "http://localhost:8733";

        /// <summary>
        /// Num err connessioni corrente
        /// </summary>
        protected int connErrCur = 0;

        /// <summary>
        /// Max num err connesisone prima di disconnettere
        /// </summary>
        protected int connErrMax = 5;

        /// <summary>
        /// LookUpTable informazioni raccolte
        /// </summary>
        protected Dictionary<string, string> RestDataLUT = new Dictionary<string, string>();

        /// <summary>
        /// Timeout chiamate REST
        /// </summary>
        protected int tOutSec = 60;

        #endregion Protected Fields

        #region Protected Properties

        /// <summary>
        /// Parametri specifici Client Rest
        /// </summary>
        protected RestParamConf restParams { get; set; } = new RestParamConf();

        #endregion Protected Properties

        #region Protected Methods

        /// <summary>
        /// verifica stato ok ovvero connected oppure open
        /// </summary>
        /// <param name="currState"></param>
        /// <returns></returns>
        protected bool checkStateOk(System.ServiceModel.CommunicationState currState)
        {
            bool answ = false;
            return answ;
        }

        /// <summary>
        /// Effettua decodifica aree memoria alla bitmap usata x MAPO
        /// </summary>
        protected virtual void decodeToBaseBitmap(ref newDisplayData currDispData)
        {
            // init a zero...
            B_input = 0;
            if (queueInEnabCurr)
            {
                /* -----------------------------------------------------
                * bitmap MAPO STD 60
                * B0: POWER_ON
                * B1: RUN
                * B2: pzCount
                * B3: allarme
                * B4: manuale
                * B5: allarme TCiclo (SLOW)
                * B6: WarmUp_CoolDown
                * B7: EmergArmed (1 = NON emergenza, 0 = emergenza)
                ----------------------------------------------------- */

#if false
                // per prima cosa controllo ping e se sia connesso...
            if (connectionOk)
            {
                B_input = 1;
                currDispData.semIn = Semaforo.SV;

                // se ho pesate in memoria nel periodo richiesto --> RUN
                if (listPesateCurr != null && listPesateCurr.Count > 0)
                {
                    B_input += (1 << 1);
                }
                // metto manuale
                else
                {
                    B_input += (1 << 4);
                }

                // accodo NON emergenza
                B_input += (1 << 7);
            }
            else
            {
                B_input = 0;
                currDispData.semIn = Semaforo.SR;
            }
#endif
            }
            else
            {
                lgDebug($"[VETO getDataItemValue] | veto attivo alle {DateTime.Now:yyyy.MM.dd HH:mm:ss}");
            }
        }

        /// <summary>
        /// Esecuzione chiamata Rest tipo GET
        /// </summary>
        /// <param name="resource">URI da chiamare</param>
        /// <param name="fastCall">Chiamata rapida (timeout rapido = 5sec)</param>
        /// <returns></returns>
        protected string ExecuteCallGet(string resource, bool fastCall = false)
        {
            string answ = "";
            if (!string.IsNullOrEmpty(resource))
            {
                if (fastCall)
                {
                    restOptStd.Timeout = TimeSpan.FromSeconds(5);
                }
                try
                {
                    // client chiamate rest
                    using (var client = new RestClient(restOptStd))
                    {
                        var actReq = new RestRequest(resource, Method.Get);
                        // effettuo vera chiamata
                        var currResp = client.Get(actReq);
                        if (currResp.StatusCode == System.Net.HttpStatusCode.OK && currResp.Content != null)
                        {
                            answ = currResp.Content ?? "";
                        }
                    }
                }
                catch (Exception exc)
                {
                    lgError($"Eccezione in ExecuteCallGet{Environment.NewLine}{exc}");
                }
            }
            return answ;
        }

        /// <summary>
        /// Esecuzione chiamata Rest tipo POST
        /// </summary>
        /// <param name="resource">URI da chiamare</param>
        /// <param name="payload">Payload da allegare (già serializzato)</param>
        /// <param name="fastCall">Chiamata rapida (timeout rapido = 5sec)</param>
        /// <returns></returns>
        protected string ExecuteCallPost(string resource, string payload, bool fastCall = false)
        {
            string answ = "";
            if (!string.IsNullOrEmpty(resource))
            {
                if (fastCall)
                {
                    restOptStd.Timeout = TimeSpan.FromSeconds(5);
                }
                try
                {
                    // client chiamate rest
                    using (var client = new RestClient(restOptStd))
                    {
                        var actReq = new RestRequest(resource, Method.Post);
                        actReq.AddJsonBody(payload);
                        // effettuo vera chiamata
                        var currResp = client.Post(actReq);
                        if (currResp.StatusCode == System.Net.HttpStatusCode.OK && currResp.Content != null)
                        {
                            answ = currResp.Content ?? "";
                        }
                    }
                }
                catch (Exception exc)
                {
                    lgError($"Eccezione in ExecuteCallPost{Environment.NewLine}{exc}");
                }
            }
            return answ;
        }

        /// <summary>
        /// restituisce URL della risorsa eventualmente completato con token o altro
        /// </summary>
        /// <param name="resName"></param>
        /// <returns></returns>
        protected string GetUrlResource(string resName)
        {
            string answ = "";
            if (restParams != null && restParams.CallList != null && restParams.CallList.Count > 0)
            {
                if (restParams.CallList.ContainsKey(resName))
                {
                    answ = restParams.CallList[resName].Url;
                    // eseguo eventuale sostituzione (se trovo "[[")
                    if (answ.Contains("[["))
                    {
                        string pattern = @"\[\[.*\]\]";
                        Regex regex = new Regex(pattern);
                        Match match = regex.Match(answ);
                        if (match.Success)
                        {
                            string key = match.Value.Replace("[[", "").Replace("]]", "");
                            // cerco nella LUT...
                            if (RestDataLUT.ContainsKey(key))
                            {
                                // sostituisco!
                                answ = answ.Replace($"[[{key}]]", RestDataLUT[key]);
                            }
                        }
                    }
                }
            }
            return answ;
        }

        /// <summary>
        /// Metodo da overridare x scrivere DAVVERO i parametri sul PLC
        /// </summary>
        /// <param name="updatedPar"></param>
        protected override void plcWriteParams(ref List<objItem> updatedPar)
        {
            lgTrace($"plcWriteParams: richiesta per {updatedPar.Count} params");
            foreach (var item in updatedPar)
            {
                lgInfo($"ITEM | {item.uid} | {item.value}");
                // salvo i valori di setup x prox volta...
                upsertKey(item.uid, item.value);
#if false
                bool fatto = false;
                bool isPesata = false;
                bool isIN = false;
                gestWeightOut answ = new gestWeightOut();
                // se è richiesta pesata IN/OUT --> mando chiamata
                if (item.uid == "reqPesata")
                {
                    isPesata = true;
                    isIN = item.reqValue.ToUpper() == "IN";
                    //isIN = item.value.ToUpper() == "IN";
                    try
                    {
                        answ = reqWeight(isIN);
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in plcWriteParams.reqWeight | isIn: {isIN}{Environment.NewLine}{exc}");
                    }
                }
                else if (item.uid == "RM" || item.uid.StartsWith("Cod"))
                {
                    // comunque segno fatto x altri casi
                    fatto = true;
                }

                // se è pesata...
                if (isPesata)
                {
                    // se è OK
                    if (answ.feedback == "C")
                    {
                        lgInfo($"reqWeight | Effettuato richiesta | {answ.feedback} | {answ.notes}");
                        // resetto pesata
                        upsertKey(item.uid, "");
                    }
                    else
                    {
                        lgError($"reqWeight | Errore in richiesta peso Rest | {answ.feedback} | {answ.notes}");
                    }
                    item.value = "";
                    item.reqValue = "";
                    item.lastRead = DateTime.Now;
                    item.UM = "";
                    // salvo esito richiesta comunque
                    upsertKey("logReq", $"{answ.feedback} | {answ.notes}");
                    // faccio in modo di eseguire subito getDynData
                    demFactDynData = 1;
                    processDynData();
                }
                else
                {
                    // se fatto --> aggiorno!
                    if (fatto)
                    {
                        //item.value = item.reqValue;
                        item.reqValue = "";
                        item.lastRead = DateTime.Now;
                        item.UM = "";
                    }
                }
#endif
            }
        }

        /// <summary>
        /// Esegue lettura dati + salvataggio in LUT
        /// </summary>
        protected virtual void refreshData()
        { }

        /// <summary>
        /// Recupero valore da cache LUT
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        protected string RestLutGet(string key)
        {
            string value = "";
            if (RestDataLUT.ContainsKey(key))
            {
                value = RestDataLUT[key];
            }
            return value;
        }

        /// <summary>
        /// Upsert in cache LUT del parametro
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        protected void RestLutUpsert(string key, string value)
        {
            if (RestDataLUT.ContainsKey(key))
            {
                RestDataLUT[key] = value;
            }
            else
            {
                RestDataLUT.Add(key, value);
            }
        }
        /// <summary>
        /// Upsert in cache LUT del parametro
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        protected void RestLutUpsert(string key, object value)
        {
            if (RestDataLUT.ContainsKey(key))
            {
                RestDataLUT[key] = $"{value}";
            }
            else
            {
                RestDataLUT.Add(key, $"{value}");
            }
        }

        protected void setupRestConf(string restConfFile)
        {
            // se ho file specifico
            if (!string.IsNullOrEmpty(getOptPar("REST_CONF")))
            {
                string rawData = "";
                // leggo file e decodifico...
                string confPath = $"{Application.StartupPath}/DATA/CONF/{restConfFile}";
                lgInfo($"Apertura file {confPath}");
                using (StreamReader sr = new StreamReader(confPath))
                {
                    rawData = sr.ReadToEnd().Replace("\n", "").Replace("\r", "");
                }
                // continuo decodifica
                if (!string.IsNullOrEmpty(rawData))
                {
                    restParams = JsonConvert.DeserializeObject<RestParamConf>(rawData);
                    if (restParams != null)
                    {
                        // inizializzo dal file di conf le variabili necessarie...
                        tOutSec = restParams.timeOutSec;
                        apiUrl = restParams.apiUrl ?? "http://localhost:8733";
                        // sistemo parametri specali come samplePeriod ed errori conn...
                        samplePeriod = restParams.samplePeriod;
                        connErrMax = restParams.connErrorMax;
                    }
                }
            }

            // sistemo conf standard x call REST
            restOptStd = new RestClientOptions
            {
                Timeout = TimeSpan.FromSeconds(tOutSec),
                BaseUrl = new Uri(apiUrl)
            };
        }

        #endregion Protected Methods

        #region Private Fields

        /// <summary>
        /// Conf client RestSharp standard:
        /// - timeout 1 min
        /// </summary>
        private RestClientOptions restOptStd = new RestClientOptions { Timeout = TimeSpan.FromSeconds(60) };

        #endregion Private Fields
    }
}