using Applications.FDC;
using Edge.Core.Database;
using Edge.Core.Processor;
using Edge.Core.IndustryStandardInterface.Pump;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using Wayne.FDCPOSLibrary;
using Edge.Core.Configuration;

namespace Applications.PumpInfoToSinoChemPosSqlServerOrMySql
{
    public abstract class AppBase
    {
        protected ILogger logger = NullLogger.Instance;
        protected FdcServerHostApp fdcServerHostApp;// = Configurator.Default.NozzleProductConfiguration.Mapping;

        protected AppConfigV1 appConfig;

        #region App config

        public class AppConfigV1
        {
            public string DatabaseConnStr { get; set; }
            public string JiaYouJiPeiZhi_TableName { get; set; }
            public List<YouJiConfigV1> YouJiConfigs { get; set; }
        }

        /// <summary>
        /// 对应一台物理油机
        /// </summary>
        public class YouJiConfigV1
        {
            /// <summary>
            /// 油机编号,由1开始编号
            ///     假设某个油站有四台油机, 则此号码分别为1、2、3、4
            /// </summary>
            public byte JiHao { get; set; }

            public List<YouMianConfigV1> YouMianConfigs { get; set; }
        }

        /// <summary>
        /// 每台物理油机有两个油面(A面或B面)
        /// </summary>
        public class YouMianConfigV1
        {
            /// <summary>
            /// 0 or 1, 0=A面 1=B面
            /// </summary>
            public int YouMianBianHao { get; set; }

            public List<YouQiangConfigV1> YouQiangConfigs { get; set; }
        }

        public class YouQiangConfigV1
        {
            /// <summary>
            /// 每台油面下的枪号,由1开始编号,作用范围是一个面,即换面则重置,重新从1开始。
            ///     如某个面上有2把枪,则此编号为1、2,另外一个面上有3把枪,则此编号为1、2、3.
            /// </summary>
            public int QiangHao { get; set; }

            /// <summary>
            /// 枪号,对应"加油流水表"和"油枪状态表"的枪号(jihao),整个油站的所有枪号由1开始编号
            ///    
            /// 应该与FDC SERVER中的site level nozzle id相匹配对应。
            /// </summary>

            public int LuoJiQiangHao { get; set; }
        }
        #endregion

        protected AppBase(AppConfigV1 appConfig, IServiceProvider services)
        {
            this.appConfig = appConfig;
            var loggerFactory = services.GetRequiredService<ILoggerFactory>();
            this.logger = loggerFactory.CreateLogger("Application");
        }

        /// <summary>
        /// Truncate table jyjpz and jy_info, and then re-insert site nozzles info to them.
        /// </summary>
        /// <param name="dbConnCreator"></param>
        /// <param name="dbCommandCreator"></param>
        /// <returns></returns>
        protected bool ReCreatePumpLayoutToDatabase(Func<DbConnection> dbConnCreator,
            Func<DbCommand> dbCommandCreator)
        {
            try
            {

                #region truncate table [jyjpz] and [jy_info], and then insert data into table [jyjpz]

                // for now, always overwrite the whole table in POS database without further check for rows or columns level.
                using (var posConn = dbConnCreator())
                {
                    /* configInFcDb structure like below */
                    //"   JiHao: " + oneRow["jiHao"] + ", sideId: " + oneRow["sideId"] +
                    //", nozzleLogicalId: " + oneRow["HoseLogicalId"] +
                    //", siteLevelNozzleId: " + oneRow["siteLevelNozzleId"]

                    string bulkInsertCmd = "";
                    const string sqlConcateAppendix = "Union all ";
                    foreach (var youJiConfig in this.appConfig.YouJiConfigs)
                    {
                        foreach (var youMianConfig in youJiConfig.YouMianConfigs)
                        {
                            foreach (var youQiangConfig in youMianConfig.YouQiangConfigs)
                            {
                                bulkInsertCmd +=
                                    $"Select {youJiConfig.JiHao}, '{youMianConfig.YouMianBianHao}', {youQiangConfig.QiangHao}, {youQiangConfig.LuoJiQiangHao} {sqlConcateAppendix}";
                            }
                        }
                    }

                    var truncateAndInsertCmd = dbCommandCreator();
                    truncateAndInsertCmd.Connection = posConn;
                    truncateAndInsertCmd.CommandText =
                            "TRUNCATE table " + this.appConfig.JiaYouJiPeiZhi_TableName + "; TRUNCATE table jy_info; INSERT " + this.appConfig.JiaYouJiPeiZhi_TableName + " (jihao, abtype, qianghao, luojiqh) " + bulkInsertCmd.Substring(0, bulkInsertCmd.Length - sqlConcateAppendix.Length);
                    logger.LogInformation("truncateAndInsertSiteConfigCmd: " + truncateAndInsertCmd.CommandText);
                    posConn.Open();
                    truncateAndInsertCmd.ExecuteNonQuery();
                    //return true;
                };

                #endregion

                #region insert data into table [jy_info]

                //foreach (var nozzle in sortedSiteNozzlesDataRows)//.Select(n => n["siteLevelNozzleId"]))
                //{
                //var siteLevelNozzleId = nozzle["siteLevelNozzleId"];
                foreach (var nozzle in fdcServerHostApp.FdcPumpControllers.SelectMany(c => c.Nozzles))
                {
                    //var pumpId = int.Parse(nozzle["pumpid"].ToString());
                    //var pump = fdcServerHostApp.FdcPumpControllers.First(f => f.PumpId == pumpId);
                    //var nozzleLogicalId = int.Parse(nozzle["HoseLogicalId"].ToString());
                    using (var posSqlConnection = dbConnCreator())
                    {
                        var bindingNozzleExtraInfo = this.fdcServerHostApp.GetNozzleExtraInfos()?.FirstOrDefault(f => f.PumpId == nozzle.PumpId
                                   && f.NozzleLogicalId == nozzle.LogicalId);
                        try
                        {

                            //var gradeFriendlyName = rawProductNameToPosProductNameMapping[productNo];// Translator.GetFriendlyGradeName(SiteConfigUtility.Default.GetGradeName(siteLevelNozzleId));
                            //SqliteDbContext dbContext = new SqliteDbContext();
                            //var lastTrx = dbContext.PumpTransactionModels.OrderByDescending(f => f.SaleEndTime)
                            //    .FirstOrDefault(f => f.PumpId == pumpId && f.LogicalNozzleId == nozzleLogicalId);
                            var lastTrx = fdcServerHostApp.GetAvailableFuelSaleTrxsWithDetailsAsync(nozzle.PumpId, nozzle.LogicalId, 1).Result.FirstOrDefault();
                            var totalizer = fdcServerHostApp.GetFuelPointTotalsAsync(nozzle.PumpId, nozzle.LogicalId).Result;
                            //new Tuple<float, float>((float)((lastTrx?.VolumeTotalizer / Math.Pow(10, pump.VolumeDecimalDigits) ?? 0)),
                            //    (float)((lastTrx?.AmountTotalizer / Math.Pow(10, pump.AmountDecimalDigits) ?? 0) * Math.Pow(10, 2)));
                            var setPumpOnStartingCommand = dbCommandCreator();
                            setPumpOnStartingCommand.Connection = posSqlConnection;
                            setPumpOnStartingCommand.CommandText = this.InitTable_jy_info_SqlCommand(
                                (bindingNozzleExtraInfo?.SiteLevelNozzleId ?? -1).ToString(),
                                (bindingNozzleExtraInfo?.ProductName ?? "undefined"),
                                totalizer.Item2, totalizer.Item1);
                            logger.LogDebug("initTable_jy_info_Command: " + setPumpOnStartingCommand.CommandText);
                            posSqlConnection.Open();
                            setPumpOnStartingCommand.ExecuteNonQuery();
                        }
                        catch (Exception ex)
                        {
                            logger.LogError("executing initTable_jy_info_Command failed for siteLevelNozzleId: " + (bindingNozzleExtraInfo?.SiteLevelNozzleId ?? -1) + ", exception detail: " + ex);
                            throw;
                        }
                    }
                }

                #endregion

            }
            catch (Exception ex)
            {
                logger.LogError("PerformCompareAndUpdate exceptioned, detail: " + ex);
                return false;
            }

            return true;
        }

        protected virtual string InitTable_jy_info_SqlCommand(string siteLevelNozzleId, string gradeFriendlyName,
            double totalizerVol, double totalizerAmt)
        {
            return string.Format("if not exists(select * from jy_info where jihao={0})" +
                                "   BEGIN" +
                                "       insert jy_info (jihao, status, youpin, qty, amount, fzqty, fzamount) values({0}, '{1}', N'{2}', 0, 0, '{3}', {4})" +
                                "   END",
                                siteLevelNozzleId,
                                'F',
                                gradeFriendlyName,
                                totalizerVol, totalizerAmt);
        }

        /// <summary>
        /// attach pump event for inserting correlated data to database tables.
        /// </summary>
        /// <param name="dbConnCreator"></param>
        /// <param name="dbCommandCreator"></param>
        /// <returns></returns>
        protected bool HandlePumpStateChangeToDatabase(Func<DbConnection> dbConnCreator,
            Func<DbCommand> dbCommandCreator)
        {
            var siteLevelNozzldIdsInAppConfig = this.appConfig.YouJiConfigs.SelectMany(youJiConfig => youJiConfig.YouMianConfigs.SelectMany(youMianConfig => youMianConfig.YouQiangConfigs))
                .Select(youQiangConfig => youQiangConfig.LuoJiQiangHao);
            var siteLevelNozzldIdsInFdcServerApp = this.fdcServerHostApp.GetNozzleExtraInfos().Where(ri => ri.SiteLevelNozzleId.HasValue).Select(ri => ri.SiteLevelNozzleId.Value);
            var missedInFdcServerApp = siteLevelNozzldIdsInAppConfig.Except(siteLevelNozzldIdsInFdcServerApp);
            var missedInAppConfig = siteLevelNozzldIdsInFdcServerApp.Except(siteLevelNozzldIdsInAppConfig);
            if (missedInFdcServerApp.Any())
            {
                throw new ArgumentException("油枪(以此App中 全站枪号 为标识): "
                    + missedInFdcServerApp.Select(n => n.ToString()).Aggregate((acc, n) => acc + ", " + n) + " 未配置于FdcServerApp中, 请配置后再试");
            }

            if (missedInAppConfig.Any())
            {
                throw new ArgumentException("油枪(以 FdcServerApp中的全站枪号 为标识): "
                    + missedInAppConfig.Select(n => n.ToString()).Aggregate((acc, n) => acc + ", " + n) + " 未配置于此App中, 请配置后再试");
            }

            fdcServerHostApp.OnStateChange += (s, a) =>
            {
                var pump = s as IFdcPumpController;
                try
                {
                    if (a.NewPumpState == LogicalDeviceState.FDC_READY)
                    {
                        /* indicate for nozzle if replaced back */
                        var sizeLevelNozzleIdsOnPump = this.fdcServerHostApp.GetNozzleExtraInfos().Where(ri => ri.PumpId == pump.PumpId && ri.SiteLevelNozzleId.HasValue).Select(ri => ri.SiteLevelNozzleId.Value);
                        if (!sizeLevelNozzleIdsOnPump.Any())
                        {
                            logger.LogInformation("Could not found any site level nozzle id defined on pump: " + pump.PumpId);
                            return;
                        }

                        using (var posSqlConnection = dbConnCreator())
                        {
                            try
                            {
                                /* idle would not carry nozzle id, so here reset all nozzles on target pump.*/
                                var setPumpOnIdleCommand = dbCommandCreator();
                                setPumpOnIdleCommand.Connection = posSqlConnection;
                                setPumpOnIdleCommand.CommandText = sizeLevelNozzleIdsOnPump.Select(
                                    siteLevelNozzleId =>
                                        string.Format(
                                            "Update jy_info set status = '{1}', qty=0, amount=0 where jihao = {0};", siteLevelNozzleId, 'F')
                                    ).Aggregate((acc, n) => acc + "    " + n);
                                logger.LogDebug("setPumpOnIdleCommand(via Fdc): " + setPumpOnIdleCommand.CommandText);
                                posSqlConnection.Open();
                                setPumpOnIdleCommand.ExecuteNonQuery();
                            }
                            catch (Exception ex)
                            {
                                logger.LogError("executing setPumpOnIdleCommand(via Fdc) failed, exception detail: " + ex);
                            }
                        }
                    }
                    else if (a.NewPumpState == LogicalDeviceState.FDC_CALLING)
                    {
                        using (var posSqlConnection = dbConnCreator())
                        {
                            try
                            {
                                var operatingNozzleLogicalId = a?.StateChangedNozzles?.FirstOrDefault()?.LogicalId ?? (byte)0;
                                var bindingNozzleExtraInfo = this.fdcServerHostApp.GetNozzleExtraInfos()?.FirstOrDefault(f => f.PumpId == pump.PumpId
                                    && f.NozzleLogicalId == operatingNozzleLogicalId);
                                //var updatePumpOnFuelingCommand =
                                //    new SqlCommand(string.Format("Update jy_info set [status] = '{1}', youpin = N'{2}', qty= {3}, amount= {4} where jihao = '{0}'"
                                //                                 , SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a?.Transaction?.Nozzle?.LogicalId ?? 0),
                                //                                 'B',
                                //                                 gradeFriendlyName,
                                //                                 a.Transaction.Volumn,
                                //                                 a.Transaction.Amount),
                                //        posSqlConnection);
                                var updatePumpOnFuelingCommand = dbCommandCreator();
                                updatePumpOnFuelingCommand.Connection = posSqlConnection;
                                updatePumpOnFuelingCommand.CommandText =
                                    string.Format("Update jy_info set status = '{1}' where jihao = '{0}';"
                                        , (bindingNozzleExtraInfo?.SiteLevelNozzleId ?? -1),
                                        'B',
                                        (bindingNozzleExtraInfo?.ProductName ?? "undefined"));
                                logger.LogDebug("updatePumpOnFuelingCommand: " + updatePumpOnFuelingCommand.CommandText);
                                posSqlConnection.Open();
                                updatePumpOnFuelingCommand.ExecuteNonQuery();
                            }
                            catch (Exception ex)
                            {
                                logger.LogError("executing updatePumpOnFuelingCommand failed, exception detail: " + ex);
                            }
                        }
                    }
                }
                catch (Exception exx) { }
            };
            fdcServerHostApp.OnCurrentFuellingStatusChange += async (s, a) =>
            {
                try
                {
                    var pump = s as IFdcPumpController;
                    if (!a.Transaction.Finished)
                    {
                        //add some random drops to reduce the operations in db, this is for performance improve.
                        if (DateTime.Now.Millisecond % 2 == 0 || DateTime.Now.Millisecond % 3 == 0) return;
                        using (var posSqlConnection = dbConnCreator())
                        {
                            try
                            {
                                var bindingNozzleExtraInfo = this.fdcServerHostApp.GetNozzleExtraInfos().FirstOrDefault(f => f.PumpId == pump.PumpId
                                    && f.NozzleLogicalId == a.Transaction.Nozzle.LogicalId);
                                //var updatePumpOnFuelingCommand =
                                //    new SqlCommand(string.Format("Update jy_info set [status] = '{1}', youpin = N'{2}', qty= {3}, amount= {4} where jihao = '{0}'"
                                //                                 , SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a?.Transaction?.Nozzle?.LogicalId ?? 0),
                                //                                 'B',
                                //                                 gradeFriendlyName,
                                //                                 a.Transaction.Volumn,
                                //                                 a.Transaction.Amount),
                                //        posSqlConnection);
                                var updatePumpOnFuelingCommand = dbCommandCreator();
                                updatePumpOnFuelingCommand.Connection = posSqlConnection;
                                updatePumpOnFuelingCommand.CommandText =
                                    string.Format("Update jy_info set youpin = N'{2}', qty= {3}, amount= {4} where jihao = '{0}';"
                                        , (bindingNozzleExtraInfo?.SiteLevelNozzleId ?? -1),
                                        "",
                                        (bindingNozzleExtraInfo?.ProductName ?? "undefined"),
                                        a.Transaction.Volumn / Math.Pow(10, pump.VolumeDecimalDigits),
                                        a.Transaction.Amount / Math.Pow(10, pump.AmountDecimalDigits));
                                logger.LogDebug("updatePumpOnFuelingCommand: " + updatePumpOnFuelingCommand.CommandText);
                                posSqlConnection.Open();
                                updatePumpOnFuelingCommand.ExecuteNonQuery();
                            }
                            catch (Exception ex)
                            {
                                logger.LogError("executing updatePumpOnFuelingCommand failed, exception detail: " + ex);
                            }
                        }
                    }
                    else
                    {

                        #region totalizer value reading

                        double volumeTotalizerValue = 0, amountTotalizerValue = 0;
                        if (a.Transaction.VolumeTotalizer.HasValue)
                        {
                            /*indicates this kind of device will carry totalizer with trx, use it directly*/
                            volumeTotalizerValue = a.Transaction.VolumeTotalizer.Value / Math.Pow(10, pump.VolumeDecimalDigits);
                            amountTotalizerValue = a.Transaction.AmountTotalizer ?? -1;
                        }
                        else
                        {
                            /*indicates this kind of device will NOT carry totalizer with trx, 
                             * have to query it*/
                            var queried = await fdcServerHostApp.GetFuelPointTotalsAsync(pump.PumpId, a.Transaction.Nozzle.LogicalId);
                            volumeTotalizerValue = queried.Item2;
                            amountTotalizerValue = queried.Item1;
                        }

                        #endregion

                        var bindingNozzleExtraInfo = this.fdcServerHostApp.GetNozzleExtraInfos()?.FirstOrDefault(f => f.PumpId == pump.PumpId
                                   && f.NozzleLogicalId == a.Transaction.Nozzle.LogicalId);
                        var posSqlConnection = dbConnCreator();
                        using (posSqlConnection)
                        {
                            try
                            {
                                //var rawProductNo = nozzleProductConfig.FirstOrDefault(f => f.PumpId == pump.PumpId
                                //    && f.NozzleLogicalId == a.Transaction.Nozzle.LogicalId)?.ProductBarcode;

                                //var totalizer = SiteConfigUtility.Default.GetTotalizer(e.Fuelling.Pump.Id, e.Fuelling.Nozzle.Id);
                                var updateFuelingTrxDoneCommand = dbCommandCreator();
                                updateFuelingTrxDoneCommand.Connection = posSqlConnection;
                                updateFuelingTrxDoneCommand.CommandText =
                                    string.Format(
                                        "insert xiaofei2 (jihao, youpin, qty, danjia, amount, xf_date, xf_time, liushuino, fzqty, fzamount)" +
                                        " values({0}, N'{1}', {2}, {3}, {4}, '{5}', '{6}', {7}, {8}, {9})",
                                        (bindingNozzleExtraInfo?.SiteLevelNozzleId ?? -1),
                                        (bindingNozzleExtraInfo?.ProductName ?? "undefined"),
                                        a.Transaction.Volumn / Math.Pow(10, pump.VolumeDecimalDigits),
                                        a.Transaction.Price / Math.Pow(10, pump.PriceDecimalDigits),
                                        a.Transaction.Amount / Math.Pow(10, pump.AmountDecimalDigits),
                                        DateTime.Now.Date.ToString("yyyy-MM-dd"),
                                        DateTime.Now.ToString("HH:mm:ss"),
                                        a.Transaction.SequenceNumberGeneratedOnPhysicalPump,
                                        volumeTotalizerValue,
                                        amountTotalizerValue);
                                logger.LogInformation("updateFuelingTrxDoneCommand: " + updateFuelingTrxDoneCommand.CommandText);
                                posSqlConnection.Open();
                                updateFuelingTrxDoneCommand.ExecuteNonQuery();
                            }
                            catch (Exception ex)
                            {
                                logger.LogError("executing updateFuelingTrxDoneCommand failed, exception detail: " + ex);
                            }
                        }

                        using (var sqlConnection = dbConnCreator())
                        {
                            try
                            {
                                //var rawProductNo = nozzleProductConfig.First(f => f.PumpId == pump.PumpId
                                //    && f.NozzleLogicalId == operatingNozzleLogicalId).ProductBarcode;
                                //var gradeFriendlyName = rawProductNameToPosProductNameMapping[rawProductNo.ToString()];
                                //var updatePumpOnFuelingCommand =
                                //    new SqlCommand(string.Format("Update jy_info set [status] = '{1}', youpin = N'{2}', qty= {3}, amount= {4} where jihao = '{0}'"
                                //                                 , SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a?.Transaction?.Nozzle?.LogicalId ?? 0),
                                //                                 'B',
                                //                                 gradeFriendlyName,
                                //                                 a.Transaction.Volumn,
                                //                                 a.Transaction.Amount),
                                //        posSqlConnection);
                                var updatePumpOnFuelingCommand = dbCommandCreator();
                                updatePumpOnFuelingCommand.Connection = sqlConnection;
                                updatePumpOnFuelingCommand.CommandText =
                                    string.Format("Update jy_info set fzqty = '{1}', fzamount={2} where jihao = '{0}'",
                                        (bindingNozzleExtraInfo?.SiteLevelNozzleId ?? -1),
                                        volumeTotalizerValue,
                                        amountTotalizerValue);
                                logger.LogDebug("updatePumpOnFuelingCommand: " + updatePumpOnFuelingCommand.CommandText);
                                sqlConnection.Open();
                                updatePumpOnFuelingCommand.ExecuteNonQuery();
                            }
                            catch (Exception ex)
                            {
                                logger.LogError("executing updatePumpOnFuelingCommand failed, exception detail: " + ex);
                            }
                        }
                    }
                }
                catch (Exception exxx) { }
            };
            return true;
        }
    }
}