using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Newtonsoft.Json;

using DataAccess;
using ResmedAVXHelper;

namespace RevampResmedService
{
    class CResmedXfer
    {
        /// <summary>
        /// Status Code
        /// </summary>
        long m_lStatusCode = 0;

        /// <summary>
        /// Status Comment
        /// </summary>
        string m_strStatusComment = String.Empty;

        /// <summary>
        /// Resmed AVX base address
        /// </summary>
        string m_strBaseAddress = String.Empty;

        /// <summary>
        /// Resmed AVX credentials
        /// </summary>
        string m_strCredentials = String.Empty;

        /// <summary>
        /// used to signal the start of a thread to process a group of patients
        /// </summary>
        int m_nPatientGroup = 0;



        /// <summary>
        /// Construct Resmed Xfer and init
        /// </summary>
        public CResmedXfer()
        {
            System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
            ServicePointManager.ServerCertificateValidationCallback = delegate(Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
            {
                return true;
            };
        }

        /// <summary>
        /// Load system settings
        /// </summary>
        /// <param name="cnn"></param>
        /// <param name="evt"></param>
        /// <returns></returns>
        private bool LoadSystemSettings(ref CDataConnection cnn,
                                        ref CLogEvent evt)
        {
            bool bSuccess = false;
            string strMsg = String.Empty;
            string strAVXBaseAddress = String.Empty;
            string strAVXCredentials = String.Empty;
            string strSVCListCapacity = String.Empty;

            //status info
            m_lStatusCode = 0;
            m_strStatusComment = String.Empty;

            //parameter list (BaseMstr.ASPSessionID, BaseMstr.ClientIP, BaseMstr.FXUserID);
            CDataParameterList plist = new CDataParameterList(String.Empty, String.Empty, 0);

            //
            CDataSet cds = new CDataSet();
            DataSet ds = cds.GetOracleDataSet(cnn,
                                              "PCK_UTL_PARAMETERS.GetSystemParametersRS",
                                              plist,
                                              out m_lStatusCode,
                                              out m_strStatusComment);

            //check response
            if (m_lStatusCode == 0)
            {
                if (ds != null)
                {
                    foreach (DataTable table in ds.Tables)
                    {
                        //get the parameters
                        foreach (DataRow row in table.Rows)
                        {
                            string strParamName = String.Empty;
                            string strParamValue = String.Empty;

                            strParamName = CDataUtils2.GetDSStringValue(row, "PARAMETER_NAME");
                            strParamValue = CDataUtils2.GetDSStringValue(row, "PARAMETER_VALUE");

                            if (!String.IsNullOrEmpty(strParamName))
                            {
                                switch (strParamName)
                                {
                                    case "RESMED_AVX_BASEADDRESS":
                                        strAVXBaseAddress = strParamValue;
                                        break;

                                    case "RESMED_AVX_CREDENTIALS":
                                        strAVXCredentials = strParamValue;
                                        break;

                                    case "RESMED_SVC_LIST_CAPACITY":
                                        strSVCListCapacity = strParamValue;
                                        break;
                                }
                            }
                        }

                        //work with the parameters...
                        if (!String.IsNullOrEmpty(strAVXBaseAddress) &&
                           !String.IsNullOrEmpty(strAVXCredentials))
                        {
                            m_strBaseAddress = strAVXBaseAddress;
                            m_strCredentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(strAVXCredentials));

                            if (String.IsNullOrEmpty(strSVCListCapacity))
                            {
                                strMsg = "Unable to read list capacity setting from database. Switching to config file...";

                                //try loading the system settings from the config file
                                try
                                {
                                    m_nPatientGroup = Convert.ToInt32(ConfigurationManager.AppSettings["ListCapacity"].ToString());
                                    strMsg += Environment.NewLine + "[CONFIG] nListCapacity " + Convert.ToString(m_nPatientGroup);
                                }
                                catch (ConfigurationErrorsException e)
                                {
                                    if (e != null)
                                    {
                                        m_nPatientGroup = 100;
                                        strMsg += Environment.NewLine + "Unable to read list capacity from config file. Switching to default values...";
                                        strMsg += Environment.NewLine + "[DEFAULT] nListCapacity " + Convert.ToString(m_nPatientGroup);
                                    }
                                }
                                catch (NullReferenceException e)
                                {
                                    if (e != null)
                                    {
                                        m_nPatientGroup = 100;
                                        strMsg += Environment.NewLine + "Unable to read list capacity from config file. Switching to default values...";
                                        strMsg += Environment.NewLine + "[DEFAULT] nListCapacity " + Convert.ToString(m_nPatientGroup);
                                    }
                                }

                                evt.EventMessages[(int)CLogEvent.EventMessage.nDETAIL_MESSAGE] = strMsg;
                                evt.LogEvent(CLogEvent.EventMessage.nDETAIL_MESSAGE, EventLogEntryType.Warning);

                            }
                            else
                            {
                                m_nPatientGroup = Convert.ToInt32(strSVCListCapacity);
                            }

                            bSuccess = true;
                        }
                        else
                        {
                            m_lStatusCode = 1;
                            strMsg = "Not all system settings are found for Resmed AVX API.";
                            bSuccess = false;
                        }

                    }
                }
                else
                {
                    m_lStatusCode = 1;
                    strMsg = "System settings not found for Resmed AVX API.";
                }
            }
            else
            {
                m_lStatusCode = 1;
                strMsg = "An error occurred while loading system settings.";
            }

            if (m_lStatusCode != 0)
            {
                evt.EventMessages[(int)CLogEvent.EventMessage.nDETAIL_MESSAGE] = strMsg;
                evt.LogEvent(CLogEvent.EventMessage.nDETAIL_MESSAGE, EventLogEntryType.Error);
            }

            return bSuccess;
        }

        /// <summary>
        /// Get list of ECN to process
        /// </summary>
        /// <returns></returns>
        public bool ImportData()
        {
            CLogEvent evt = new CLogEvent();

            CDataConnection cnn = null;
            string strConnectionString = String.Empty;
            bool bAudit = true;


            DataSet dsECNList = null;

            long lThreadGroups = 0;
            long lThreadGroupCount = 0;
            CResmedXferList xferList = null;
            CResmedXferItem xfer = null;

            //config settings
            try
            {
                string strUseCnnConfig = "DBConnString_" + ConfigurationManager.AppSettings["APP_TARGET_ENV"];
                strConnectionString = ConfigurationManager.ConnectionStrings[strUseCnnConfig].ConnectionString;
                bAudit = (ConfigurationManager.AppSettings["AUDIT"] == "1") ? true : false;
            }
            catch (ConfigurationErrorsException e)
            {
                m_lStatusCode = 1;
                m_strStatusComment = evt.EventMessages[(int)CLogEvent.EventMessage.nERROR_THREAD_CONFIG_FILE] + " " + e.Message;
                evt.LogEvent(CLogEvent.EventMessage.nERROR_THREAD_CONFIG_FILE, EventLogEntryType.Error);
                return false;
            }

            //connect to data source
            cnn = new CDataConnection();
            if (!cnn.Connect(strConnectionString, (int)DataConnectionType.Oracle, bAudit))
            {
                m_lStatusCode = 1;
                m_strStatusComment = evt.EventMessages[(int)CLogEvent.EventMessage.nERROR_CONNECTING_DB];
                evt.LogEvent(CLogEvent.EventMessage.nERROR_CONNECTING_DB, EventLogEntryType.Error);
                return false;
            }

            if (!LoadSystemSettings(ref cnn, ref evt))
            {
                //close connection... it is not needed for now.
                cnn.Close();
                return false;
            }

            //first, check if the Resmed AVX API service is active
            if (!IsExternalServiceActive())
            {
                evt.LogEvent(CLogEvent.EventMessage.nRESMED_AVX_UNAVAILABLE, EventLogEntryType.Information);
                return true;
            }

            //Resmed AVX API service is active!
            //get ECN list to  start processing data...
            evt.LogEvent(CLogEvent.EventMessage.nSEARCHING_LIST, EventLogEntryType.Information);
            dsECNList = GetResmedItemListDS(ref cnn);

            //close connection... it is not needed for now.
            cnn.Close();

            //check and process ECN list
            if (dsECNList == null)
            {
                evt.LogEvent(CLogEvent.EventMessage.nERROR_NO_LIST_RECEIVED, EventLogEntryType.Error);
            }
            else
            {
                string strMsg = String.Empty;

                if (dsECNList.Tables[0].Rows.Count > 0)
                {
                    lThreadGroups = Convert.ToInt64(Math.Ceiling((decimal)dsECNList.Tables[0].Rows.Count / m_nPatientGroup));

                    strMsg = "Found list with a total of {0} items." +
                         Environment.NewLine + "The list will be process using {1} threads of {2} items each.";

                    strMsg = String.Format(strMsg, dsECNList.Tables[0].Rows.Count, lThreadGroups, m_nPatientGroup);

                    evt.EventMessages[(int)CLogEvent.EventMessage.nDETAIL_MESSAGE] = strMsg;
                    evt.LogEvent(CLogEvent.EventMessage.nDETAIL_MESSAGE, EventLogEntryType.Information);
                }
                else
                {
                    evt.LogEvent(CLogEvent.EventMessage.nEMPTY_LIST, EventLogEntryType.Information);
                    return true;
                }

                //PROCESS ECN LIST
                DateTime dtStart = System.DateTime.Now;
                DateTime dtEnd = System.DateTime.Now;

                strMsg = "Import Data process started at " + dtStart.ToString() + ".";
                evt.EventMessages[(int)CLogEvent.EventMessage.nDETAIL_MESSAGE] = strMsg;
                evt.LogEvent(CLogEvent.EventMessage.nDETAIL_MESSAGE, EventLogEntryType.Information);

                //create new list that will become an individual thread
                xferList = new CResmedXferList(m_strBaseAddress, m_strCredentials);

                var handles = new ManualResetEvent[lThreadGroups];
                lThreadGroupCount = 0;

                //loop over each ECN
                foreach (DataRow dr in dsECNList.Tables[0].Rows)
                {
                    if (xferList.Count >= m_nPatientGroup)
                    {
                        handles[lThreadGroupCount] = new ManualResetEvent(false);

                        //queue the list for thread transfer
                        xferList.QueueTransfer(ref handles[lThreadGroupCount]);

                        //make a new list for the next group
                        xferList = new CResmedXferList(m_strBaseAddress, m_strCredentials);

                        lThreadGroupCount++;
                    }

                    //new search item
                    xfer = new CResmedXferItem();

                    //load search item
                    if (!dr.IsNull("PATIENT_ID"))
                    {
                        xfer.PatientID = dr["PATIENT_ID"].ToString();
                    }

                    if (!dr.IsNull("EXTERNAL_ID"))
                    {
                        xfer.ExternalID = dr["EXTERNAL_ID"].ToString();
                    }

                    if (!dr.IsNull("CPAP_GAP"))
                    {
                        xfer.DaysGap = Convert.ToInt64(dr["CPAP_GAP"].ToString());
                    }

                    if (!dr.IsNull("PROCESS_DATE"))
                    {
                        xfer.ProcessDate = CDataUtils2.GetDSDateTimeValue(dr, "PROCESS_DATE").ToString("yyyy-MM-dd");
                    }

                    //add the item to the list of items to transfer
                    xferList.Add(xfer);

                }

                //queue up any remaining patients
                if (xferList != null)
                {
                    if (xferList.Count > 0)
                    {
                        handles[lThreadGroupCount] = new ManualResetEvent(false);

                        //queue the list for thread transfer
                        xferList.QueueTransfer(ref handles[lThreadGroupCount]);

                        //make a new list for the next group
                        xferList = new CResmedXferList(m_strBaseAddress, m_strCredentials);

                        lThreadGroupCount++;
                    }
                }

                //Wait for all thread to finish and then display a final message...
                WaitHandle.WaitAll(handles);

                dtEnd = System.DateTime.Now;
                strMsg = "Data Import process completed at {0}. Total processing time was {1}.";
                strMsg = String.Format(strMsg, dtEnd.ToString(), (dtEnd - dtStart).ToString(@"dd\.hh\:mm\:ss\.ffffff"));
                evt.EventMessages[(int)CLogEvent.EventMessage.nDETAIL_MESSAGE] = strMsg;
                evt.LogEvent(CLogEvent.EventMessage.nDETAIL_MESSAGE, EventLogEntryType.Information);

            }

            return true;

        }

        /// <summary>
        /// Get Resmed Xfer item list. Get ECN list.
        /// </summary>
        /// <param name="cnn"></param>
        /// <returns></returns>
        private DataSet GetResmedItemListDS(ref CDataConnection cnn)
        {
            //status info
            m_lStatusCode = 0;
            m_strStatusComment = String.Empty;

            CDataParameterList plist = new CDataParameterList(String.Empty, String.Empty, 0);

            //
            CDataSet cds = new CDataSet();
            DataSet ds = cds.GetOracleDataSet(cnn,
                                              "PCK_XFER.GetResmedItemListRS",
                                              plist,
                                              out m_lStatusCode,
                                              out m_strStatusComment);

            if (m_lStatusCode != 0)
            {
                return null;
            }

            return ds;
        }

        /// <summary>
        /// Implement how to check if the Resmed AVX service is active
        /// </summary>
        /// <returns></returns>
        private bool IsExternalServiceActive()
        {
            bool bActive = false;

            CHealtCheckMainResponse mainRespHealthCheck = null;

            //status info
            m_lStatusCode = 0;
            m_strStatusComment = String.Empty;

            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(m_strBaseAddress);
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                HttpResponseMessage response = null;

                try
                {
                    response = client.GetAsync("healthCheck/status/").Result;
                    if (response.IsSuccessStatusCode)
                    {
                        mainRespHealthCheck = response.Content.ReadAsAsync<CHealtCheckMainResponse>().Result;
                        bActive = true;
                    }
                    else
                    {
                        m_lStatusCode = 1;
                        m_strStatusComment = response.ReasonPhrase;
                    }
                }
                catch (System.Net.Http.HttpRequestException e)
                {
                    if (!String.IsNullOrEmpty(e.Message))
                    {
                        m_lStatusCode = 1;
                        m_strStatusComment = e.Message;
                    }
                    if (e.InnerException != null)
                    {
                        if (!String.IsNullOrEmpty(e.InnerException.Message))
                        {
                            m_strStatusComment += " " + e.InnerException.Message;
                        }
                    }
                }
                catch (System.AggregateException e)
                {
                    m_lStatusCode = 1;
                    m_strStatusComment = e.Message;
                    if (e.InnerException != null)
                    {
                        if (!String.IsNullOrEmpty(e.InnerException.Message))
                        {
                            m_strStatusComment += " " + e.InnerException.Message;
                        }
                    }
                }
                catch (Exception e)
                {
                    m_strStatusComment = e.ToString();
                }

            }

            return bActive;

        }
    }
}
