using Edge.Core.Processor;
using Edge.Core.IndustryStandardInterface.Pump;
using Dfs.WayneChina.HengshanPayTerminal;
using Dfs.WayneChina.HengshanPayTerminal.MessageEntity;
using Dfs.WayneChina.HengshanPayTerminal.MessageEntity.Incoming;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Dfs.WayneChina.HengshanPayTerminal.Support;
using Applications.FDC;
using Edge.Core.Database;
using FdcServerHost;
using Dfs.WayneChina.CardTrxManager.TrxSubmitter;
using Dfs.WayneChina.CardTrxManager;
using System.Threading.Tasks;
using Dfs.WayneChina.IPosPlus.ServiceClient;
using Dfs.WayneChina.SpsDataCourier;
using NLog.LayoutRenderers.Wrappers;
using Edge.Core.Configuration;
using Edge.Core.Processor.Dispatcher.Attributes;

namespace Dfs.WayneChina.IPosPlus
{
    /// <summary>
    /// Serves as an application controller containing the terminal handlers and terminal managers,
    /// as well as the database access manager.
    /// </summary>
    [MetaPartsDescriptor(
    "lang-zh-cn:iPosPlus应用lang-en-us:iPOS Plus App",
    "lang-zh-cn:用于管理加油机IC卡终端控制" +
    "lang-en-us:Used for managing all IC card terminals on dispensers across the site",
        new[] { "lang-zh-cn:加油机lang-en-us:Pump", "lang-zh-cn:IfsfFdcServerlang-en-us:IfsfFdcServer" })]
    public class IPosPlusApp : IAppProcessor
    {
        #region Fields

        private SpsDbManager.SpsManager spsManager;

        private List<HengshanPayTermHandler> paymentTerminalHandlers = new List<HengshanPayTermHandler>();

        private List<TerminalManager> terminalManagers = new List<TerminalManager>();

        private NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("IPosPlusApp");

        private PosInitialData initalData;

        private object syncObj = new object();

        private FdcServerHostApp fdcServerApp;

        private string fuelProductMapping;

        private IEnumerable<NozzleExtraInfo> nozzleProductConfig = Configurator.Default.NozzleExtraInfoConfiguration.Mapping;

        private Dictionary<int, int> fuelMappingDict = new Dictionary<int, int>();

        private List<FillingInfo> customerFillings = new List<FillingInfo>();

        private List<TrxSubmitter> submitters = new List<TrxSubmitter>();

        private CloudCredential cloudCredential;

        private bool onlineDiscount = false;
        private int discountTimeout = 3;

        private string promotionCategories = string.Empty;

        private Dictionary<int, string> fuelNameDict = new Dictionary<int, string>();
        private Dictionary<string, uint> fuelCodePriceDict = new Dictionary<string, uint>();

        public int CardAppType { get; }

        private int interval;

        private Dictionary<int, bool> pumpPriceDict = new Dictionary<int, bool>();
        private object dictSync = new object();

        private SpsDataCourierApp spsDataCourierApp;

        private StoreForwardManager sfManager;
        private int stationNo = 0;
        private string stationName = "";
        private int mode = 1;
        private int reservedBalance = 20;

        #endregion

        #region MySQL connection string

        private string _mysqlConn = 
            "server=localhost;port=3306;user=root;password=HS1205;database=sps_db;TreatTinyAsBoolean=false;Convert Zero Datetime=True";

        #endregion

        #region Constructor

        [ParamsJsonSchemas("appCtorParamsJsonSchema")]
        public IPosPlusApp(PosAppContructorParameterV1 config)
        {
            PosId = config.PosId;
            _mysqlConn = FormatConnectionString(config.ConnectionString);
            spsManager = new SpsDbManager.SpsManager(_mysqlConn);
            //Current Business Unit Id.
            var fakeBuId = Configurator.Default
                .MetaConfiguration
                .Parameter?
                .FirstOrDefault(p => p.Name.Equals("serialNumber", StringComparison.OrdinalIgnoreCase))?.Value;

            cloudCredential = new CloudCredential
            {
                UserName = config.Username,
                Password = config.Password,
                AuthServiceBaseUrl = config.AuthServiceBaseUrl,
                TransactionServiceBaseUrl = config.TransactionServiceBaseUrl,
                DiscountServiceBaseUrl = config.DiscountServiceBaseUrl,
                DeviceSN = config.DeviceSN,
                CurrentBuId = fakeBuId
            };

            onlineDiscount = config.OnlineDiscount;
            discountTimeout = config.DiscountTimeout;
            if (this.onlineDiscount)
            {
                DiscountServiceClient = new DiscountServiceClient(this, cloudCredential, promotionCategories, discountTimeout);
            }
            promotionCategories = config.PromoCategories;
            fuelProductMapping = string.Join(";", config.FuelMappingArr.Select(m => $"{m.Barcode}:{m.FuelNo}"));
            CardAppType = config.CardAppType;
            interval = config.Interval;
            mode = config.PumpMode;
            stationNo = config.StationNo;
            stationName = config.StationName;
            reservedBalance = config.ReservedBalance;

            SetFuelProductMapping();
            pumpPriceDict.Clear();
            sfManager = new StoreForwardManager(cloudCredential);
        }

        public IPosPlusApp(int posId, string username, string password, string authServiceBaseUrl, string transactionServiceBaseUrl,
            string discountServiceBaseUrl, bool onlineDiscount, int discountTimeout, string deviceSN, string promoCategories, List<FuelMappingV1> fuelMappingArr,/*string fuelMapping,*/
            int cardAppType, int reservedBalance, int interval, SpsDbConnectionSetting connectionString)
        {
            PosId = posId;
            _mysqlConn = FormatConnectionString(connectionString);//connectionString;
            spsManager = new SpsDbManager.SpsManager(_mysqlConn);

            //Current Business Unit Id.
            var fakeBuId = Configurator.Default
                .MetaConfiguration
                .Parameter?
                .FirstOrDefault(p => p.Name.Equals("serialNumber", StringComparison.OrdinalIgnoreCase))?.Value;

            cloudCredential = new CloudCredential
            {
                UserName = username,
                Password = password,
                AuthServiceBaseUrl = authServiceBaseUrl,
                TransactionServiceBaseUrl = transactionServiceBaseUrl,
                DiscountServiceBaseUrl = discountServiceBaseUrl,
                DeviceSN = deviceSN,
                CurrentBuId = fakeBuId
            };

            this.onlineDiscount = onlineDiscount;//Convert.ToBoolean(onlineDiscount);
            this.discountTimeout = discountTimeout;
            promotionCategories = promoCategories;

            if (this.onlineDiscount)
            {
                DiscountServiceClient = new DiscountServiceClient(this, cloudCredential, promotionCategories, discountTimeout);
            }

            fuelProductMapping = string.Join(";", fuelMappingArr.Select(m => $"{m.Barcode}:{m.FuelNo}")); //fuelMapping;
            CardAppType = cardAppType;
            this.interval = interval;
            this.reservedBalance = reservedBalance;

            SetFuelProductMapping();
            pumpPriceDict.Clear();
            sfManager = new StoreForwardManager(cloudCredential);
        }

        private string FormatConnectionString(SpsDbConnectionSetting spsDbConnectionSetting)
        {
            return $"server={spsDbConnectionSetting.Server};port={spsDbConnectionSetting.Port};uid={spsDbConnectionSetting.Username};password={spsDbConnectionSetting.Password};database=sps_db;TreatTinyAsBoolean=false;Convert Zero Datetime=true;";
        }

        #endregion

        #region IApplication interface implementation


        public string MetaConfigName { get; set; }
        public void Init(IEnumerable<IProcessor> processors)
        {
            foreach (dynamic p in processors)
            {
                if (p is IAppProcessor)
                {
                    FdcServerHostApp fdcServer = p as FdcServerHostApp;
                    if (fdcServer != null)
                        fdcServerApp = p;

                    SpsDataCourierApp dataCourier = p as SpsDataCourierApp;
                    if (dataCourier != null)
                        spsDataCourierApp = dataCourier;

                    continue;
                }

                var handler = p.Context.Handler;

                if (handler is HengshanPayTermHandler)
                {
                    paymentTerminalHandlers.Add(handler);
                }
            }

            SetupTerminalManagers();
            SetupTransactionSubmitters();
            GetInitialData();
            GetStationInfo();

            if (fdcServerApp != null)
            {
                fdcServerApp.OnCurrentFuellingStatusChange += FdcServerApp_OnCurrentFuellingStatusChange;
            }
        }

        private bool UpsertSpsConfig()
        {
            if (fdcServerApp == null)
            {
                logger.Error($"Could not get FdcServer instance");
                return false;
            }

            var allNozzleExtraInfo = fdcServerApp.GetNozzleExtraInfos();

            var productNames = allNozzleExtraInfo.Select(n => n.ProductName).Distinct().ToList();
            //filter out the product names
            foreach (var item in productNames)
            {
                var tank = allNozzleExtraInfo.FirstOrDefault(n => n.ProductName == item)?.TankNumber;
                logger.Info($"Distinct product name: {item}, related tank: {tank}");
            }

            //filter out the tanks
            var tanks = allNozzleExtraInfo.Select(n => n.TankNumber).Distinct();
            foreach (var item in tanks)
            {
                logger.Info($"Distinct tank number: {item}");
            }

            var siteProducts = allNozzleExtraInfo.Select(n => new { Barcode = n.ProductBarcode, Name = n.ProductName }).ToList();

            var fuelBarcodes = allNozzleExtraInfo.Select(n => n.ProductBarcode).Distinct().ToList();

            if (fuelBarcodes.Count > tanks.Count())
            {
                logger.Error($"More fuel product types, less tanks, abort!");
                return false;
            }

            foreach (var product in siteProducts)
            {
                logger.Info($"Update product: {product.Barcode}, {product.Name}");
                
                var fuelCode = fuelMappingDict[product.Barcode]; // Code for fuel product, e.g. 1021, 2001
                string fuelClassNo = "1000";
                if (fuelCode >= 1000 && fuelCode < 2000)
                    fuelClassNo = "1000";
                else if (fuelCode >= 2000)
                    fuelClassNo = "2000";

                spsManager.AddOrUpdateFuelProduct(Convert.ToString(fuelCode), fuelClassNo, product.Name, 999);
            }

            //Tank product
            var tankProducts = new Dictionary<int, string>();
            foreach (var tank in tanks)
            {
                //product barcode for the tank
                var productBarcode = allNozzleExtraInfo.First(n => n.TankNumber == tank.Value).ProductBarcode;
                if (!tankProducts.ContainsKey(tank.Value))
                {
                    string fuelNo = Convert.ToString(fuelMappingDict[productBarcode]);
                    logger.Info($"Add to tankProducts Dict, tank.value: {tank.Value}, fuelno: {fuelNo}");
                    tankProducts.Add(tank.Value, fuelNo); // tank_no, fuel_no
                }
            }

            foreach (var item in tankProducts)
            {
                spsManager.AddOrUpdateTankConfig(item.Key, item.Value);
                logger.Info($"add or update tank config: {item.Key}, {item.Value}");
            }


            foreach (var tankProduct in tankProducts)
            {
                logger.Info($"tank id: {tankProduct.Key}, tank product code: {tankProduct.Value}");
            }

            // filter out the pumps
            var pumps = allNozzleExtraInfo.Select(n => n.PumpId).Distinct();
            foreach (var pump in pumps)
            {
                logger.Info($"Distinct pump id: {pump}");

                foreach (var ptHandler in paymentTerminalHandlers)
                {
                    if (ptHandler.AssociatedPumpIds.Contains(pump))
                    {
                        int serialPort = 4;
                        string portName = ptHandler.CommIdentity;
                        logger.Info($"Communicator identity: {portName}");

                        if (!string.IsNullOrEmpty(portName))
                        {
                            string name = "";
                            for (int i = 0; i < portName.Length; i++)
                            {
                                if (Char.IsDigit(portName[i]))
                                    name += portName[i];
                            }

                            if (name.Length > 0)
                            {
                                int.TryParse(name, out serialPort);

                                if (serialPort == 0)
                                    serialPort = 4;
                            }

                            if (portName.Contains('.'))
                            {
                                string[] sections = portName.Split(':');
                                name = sections.LastOrDefault();
                                int.TryParse(name, out serialPort);
                                int originalPort = serialPort;
                                serialPort = originalPort % 100;
                                logger.Info($"Parsed with dot, original: {originalPort} serialPort: {serialPort}");
                                if (serialPort == 0)
                                    serialPort = 4;
                            }
                        }
                        else
                        {
                            serialPort = ptHandler.AssociatedPumpIds.Max();
                        }

                        var subAddress = ptHandler.GetSubAddressForPump(pump);
                        logger.Info($"Sub Address: {subAddress}, COM: {serialPort} for Pump: {pump}");

                        spsManager.AddOrUpdatePump(mode, Convert.ToByte(pump), serialPort, Convert.ToByte(subAddress), 999999);
                        logger.Info($"Updating pump: {pump}, sub address: {subAddress}");
                    }
                }
            }

            foreach (var nozzleExtraInfo in allNozzleExtraInfo)
            {
                logger.Info($"PumpId: {nozzleExtraInfo.PumpId}, NozzleLogicalId: {nozzleExtraInfo.NozzleLogicalId}, " +
                    $"SiteLevelNozzleId: {nozzleExtraInfo.SiteLevelNozzleId}, ProductBarcode: {nozzleExtraInfo.ProductBarcode}, " +
                    $"ProductName: {nozzleExtraInfo.ProductName}, TankNumber: {nozzleExtraInfo.TankNumber}");


                byte nozzleId = Convert.ToByte(nozzleExtraInfo.SiteLevelNozzleId);
                byte pumpId = Convert.ToByte(nozzleExtraInfo.PumpId);
                byte tankId = Convert.ToByte(nozzleExtraInfo.TankNumber);
                string fuelNo = tankProducts[nozzleExtraInfo.TankNumber.Value];
                
                spsManager.AddOrUpdateNozzle(nozzleId, pumpId, tankId, fuelNo);

                logger.Info($"Updating NozzleId: {nozzleId}, PumpId: {pumpId}, TankId: {tankId}, FuelNo: {fuelNo}");
            }

            spsManager.UpdateGeneralInfoVersion();
            spsManager.UpdateFuelPriceVersion();

            logger.Info($"Updating station no: {stationNo}, station name: {stationName}");
            spsManager.UpdateStationInfo(stationNo, stationName);

            return true;
        }

        private void FdcServerApp_OnCurrentFuellingStatusChange(object sender, FdcServerTransactionDoneEventArg e)
        {
            if (e.Transaction.Finished)
            {
                InfoLog($"There is a finished filling, ReleaseToken: {e.ReleaseToken}, PumpId: {e.Transaction.Nozzle.PumpId}," +
                    $" LogicalNozzleId: {e.Transaction.Nozzle.LogicalId}, SequenceNo: {e.Transaction.SequenceNumberGeneratedOnPhysicalPump}");

                Task.Run(async () => await HandleCustomerCardFillings(e));
            }
        }

        //初步定位在这触发
        private async Task HandleCustomerCardFillings(FdcServerTransactionDoneEventArg e)
        {
            int nozzleNoForCurrentTrx = e.Transaction.Nozzle.PumpId;
            byte currentPumpId = Convert.ToByte(e.Transaction.Nozzle.PumpId);
            var pth = paymentTerminalHandlers.First(h => h.AssociatedPumpIds.Contains(currentPumpId));
            if (pth != null)
            {
                var nozzleNos = pth.PumpSiteNozzleNoDict[currentPumpId];
                foreach (var item in nozzleNos)
                {
                    if (pth.NozzleLogicIdDict[item] == e.Transaction.Nozzle.LogicalId)
                    {
                        nozzleNoForCurrentTrx = item;
                    }
                }
            }


            //调整当前加油记录List 加上日期的筛选 -45天前的记录
            var filling = customerFillings.OrderByDescending(p => p.StartTime).FirstOrDefault(f => f.SequenceNo == e.Transaction.SequenceNumberGeneratedOnPhysicalPump &&
                f.PumpId == e.Transaction.Nozzle.PumpId && f.NozzleId == nozzleNoForCurrentTrx && f.StartTime > DateTime.Now.AddDays(-45));

            if (filling != null)
            {
                InfoLog("Locking transaction");

                var lockedTrx = await fdcServerApp.LockFuelSaleTrxAndNotifyAllFdcClientsAsync(100, e.Transaction.Nozzle.PumpId, filling.SequenceNo, e.ReleaseToken.Value);

                if (lockedTrx == null)
                {
                    InfoLog("Amazing, failed to lock transaction in FdcServer, try again in 100 ms");
                    await Task.Delay(100);
                    lockedTrx = await fdcServerApp.LockFuelSaleTrxAndNotifyAllFdcClientsAsync(100, e.Transaction.Nozzle.PumpId, filling.SequenceNo, e.ReleaseToken.Value);

                    if (lockedTrx == null)
                    {
                        InfoLog("Locking transaction failed in second try");
                        await Task.Delay(100);
                        lockedTrx = await fdcServerApp
                            .LockFuelSaleTrxAndNotifyAllFdcClientsAsync(100, e.Transaction.Nozzle.PumpId, filling.SequenceNo, e.ReleaseToken.Value);
                    }
                }

                InfoLog("Ready to submit the customer card transaction!");

                var submitter = GetSubmitter(filling.PumpId);

                if (submitter != null)
                {
                    InfoLog("Grabbed instance of submitter");

                    var cardAccountInfo = spsManager.GetCardAccountInfo(filling.CardNo);

                    var result = await submitter.SubmitTrxAsync(new ClientTrxInfo
                    {
                        PumpId = filling.PumpId,
                        NozzleId = 1,
                        SiteNozzleNo = filling.NozzleId,//filling.PumpId,
                        Barcode = GetBarcode(filling.FuelProductCode),
                        Volume = Convert.ToDecimal(filling.Volume) / 100,
                        Amount = Convert.ToDecimal(filling.FillingAmount) / 100,
                        PayAmount = Convert.ToDecimal(filling.PayAmount) / 100,
                        UnitPrice = Convert.ToDecimal(filling.UnitPrice) / 100,
                        CardNo = filling.CardNo,
                        CurrentCardBalance = Convert.ToDecimal(filling.CardBalance) / 100,
                        FdcSqNo = e.ReleaseToken.Value,
                        SeqNo = filling.SequenceNo,
                        FuelingStartTime = filling.StartTime,
                        FuelingFinishedTime = filling.EndTime,
                        VolumeTotalizer = Convert.ToDecimal(filling.VolumeTotal) / 100,
                        CardHolder = cardAccountInfo?.Holder,
                        AccountName = cardAccountInfo?.BelongTo
                    });

                    InfoLog($"Submit successfully? {result}");

                    if (result)
                    {
                        InfoLog("Clearing transaction");

                        await fdcServerApp.ClearFuelSaleTrxAndNotifyAllFdcClientsAsync(e.Transaction.Nozzle.PumpId,
                            filling.SequenceNo.ToString(), e.ReleaseToken.Value, "100");

                        //After uploading, remove it.
                        customerFillings.Remove(filling);
                    }
                }
            }
        }

        public void AddCustomerCardFilling(FillingInfo fillingInfo)
        {
            customerFillings.Add(fillingInfo);
        }

        private void GetInitialData()
        {
            var result = spsManager.GetInitialData(PosId);

            if (result != null)
            {
                initalData = new PosInitialData
                {
                    BillNo = result.Bill_No,
                    ShiftNo = result.Shift_No,
                    ShiftState = result.Shift_State
                };
            }
        }

        private void GetStationInfo()
        {
            var stationData = spsManager.GetStationInfo();
            if (stationData != null)
            {
                StationInfo = new StationInfo { StationNo = stationData.Sno };
            }
        }

        private void UpdateStationInfo(int stationNo, string name)
        {
            spsManager.UpdateStationInfo(stationNo, name);
        }

        public int GetNextBillNo()
        {
            lock (syncObj)
            {
                return ++initalData.BillNo;
            }
        }

        public Task<bool> Start()
        {
            //Hook up the terminal message event handler
            foreach (var handler in paymentTerminalHandlers)
            {
                handler.OnTerminalMessageReceived += Handler_OnTerminalMessageReceived;
                handler.OnFuelPriceChangeRequested += Handler_OnFuelPriceChangeRequested;
            }

            InitFuelName();
            InitFuelPrices();

            if (spsDataCourierApp != null)
                logger.Info("SpsDataCourier instance exists!");

            spsManager.Start();

            sfManager.Start();
            sfManager.OnStoreForwardCompleted += SfManagerStoreForwardCompleted;

            // Inserting configuration data into sps_db.
            var result = UpsertSpsConfig();
            if (result == false)
                return Task.FromResult(false);

            return Task.FromResult(true);
        }

        private async void SfManagerStoreForwardCompleted(object sender, StoreForwardCompletedEventArgs e)
        {
            logger.Info($"SF completed, clear transaction, pump id: {e.PumpdId}, sq no: {e.SeqNo}, rt: {e.ReleaseToken}");
            await fdcServerApp.ClearFuelSaleTrxAndNotifyAllFdcClientsAsync(e.PumpdId, e.SeqNo.ToString(), e.ReleaseToken, "100");
        }

        private void Handler_OnFuelPriceChangeRequested(object sender, FuelPriceChangeRequestEventArgs e)
        {
            logger.Info($"PosApp: {e.PumpId}, {e.NozzleId}, {e.Price}");
            var result = spsManager.GetFuelPriceConfig();

            var fdcServerNozzleProductConfig = fdcServerApp.GetNozzleExtraInfos();

            var nozzle = fdcServerNozzleProductConfig.First(c => c.PumpId == e.PumpId && c.NozzleLogicalId == e.NozzleId);
            logger.Info($"PosApp: Nozzle, barcode: {nozzle.ProductBarcode}");

            var fuelNo = GetFuelNo(nozzle.ProductBarcode);
            logger.Info($"PosApp: Got Fuel No: {fuelNo}");

            spsManager.UpdateFuelPrice(Convert.ToString(fuelNo), e.Price);

            lock (dictSync)
            {
                pumpPriceDict[e.PumpId] = true;

                if (pumpPriceDict.Count(p => p.Value == false) > 1)
                {
                    return;
                }
                else if (pumpPriceDict.Count(p => p.Value == false) == 1)
                {
                    logger.Info("PosApp: FuelPrice dict, only one pump left, update price version");
                    spsManager.UpdateFuelPriceVersion();
                }
                else
                {
                    // After all pumps' requests handled, reset the dictionary
                    foreach (var item in pumpPriceDict.Keys.ToList())
                    {
                        pumpPriceDict[item] = false;
                    }
                }
            }

            InitFuelPrices();
        }

        public Task<bool> Stop()
        {
            //Unhook the terminal message event handler
            foreach (var handler in paymentTerminalHandlers)
            {
                handler.OnTerminalMessageReceived -= Handler_OnTerminalMessageReceived;
                handler.OnFuelPriceChangeRequested -= Handler_OnFuelPriceChangeRequested;
            }

            if (fdcServerApp != null)
            {
                fdcServerApp.OnCurrentFuellingStatusChange -= FdcServerApp_OnCurrentFuellingStatusChange;
            }

            return Task.FromResult(true);
        }

        public void LockUnlockPump(int pumpId, LockUnlockOperation operation)
        {
            var terminalManager = GetTerminalManager(pumpId);
            terminalManager.LockUnlockPump(operation);
        }

        private async void Handler_OnTerminalMessageReceived(object sender, TerminalMessageEventArgs e)
        {
            await HandleTerminalMessage(e);
        }

        private async Task HandleTerminalMessage(TerminalMessageEventArgs e)
        {
            var message = e.Message;

            DebugLog($"Incoming terminal message, HandlerGroup:[{e.Identifier}], Source {message.SourceAddress}, " +
                $"Destination {message.DestinationAddress}, FrameSeqNoByte {message.FrameSqNoByte}");

            var terminalManager = GetTerminalManager(e.Identifier, message.DestinationAddress);
            if (terminalManager == null)
            {
                logger.Error($"Could not find TerminalManager for message of identifier: {e.Identifier}, dest addr: {message.DestinationAddress}");
                return;
            }

            DebugLog($"Terminal Manager {terminalManager.PumpId} handle the message");

            if (message is RegisterRequest)
            {
                terminalManager.HandleRegiser((RegisterRequest)message);
                DebugLog($"RegisterRequest handled, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
            }
            else if (message is CheckCmdRequest)
            {
                DebugLog($"CheckCmdRequest, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
                terminalManager.HandleCheckCmdRequest((CheckCmdRequest)message);
                DebugLog("Sent checkcmdResponse");
            }
            else if (message is ValidateCardRequest)
            {
                terminalManager.HandleCardValidation((ValidateCardRequest)message);
                DebugLog($"ValidateCardRequest, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
            }
            else if (message is AuthRequest)
            {
                terminalManager.HandlAuthorization((AuthRequest)message);
                DebugLog($"AuthRequest handled, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
            }
            else if (message is FuelingDataRequest)
            {
                terminalManager.HandleFuelingData((FuelingDataRequest)message);
                DebugLog($"FuelingDataRequest handled, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
            }
            else if (message is PaymentRequest)
            {
                terminalManager.HandlePaymentRequest((PaymentRequest)message);
                DebugLog($"PaymentRequest handled, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
            }
            else if (message is TransactionDataRequest)
            {
                await terminalManager.HandleTransactionData((TransactionDataRequest)message);
                DebugLog($"TransactionDataRequest handled, Source {message.SourceAddress}, Destination {message.DestinationAddress}");
            }
            else if (message is LockOrUnlockPumpAck)
            {
                terminalManager.HandleLockUnlockPumpResult((LockOrUnlockPumpAck)message);
                DebugLog($"LockOrUnlock ack, Source: {message.SourceAddress}, Destination: {message.DestinationAddress}, " +
                    $"Operation: {((LockOrUnlockPumpAck)message).OperationType}, Result state: {((LockOrUnlockPumpAck)message).DispenserState}");
            }
            else if (message is DataDownloadRequest)
            {
                terminalManager.HandleDataDownloadRequest((DataDownloadRequest)message);
            }
            else if (message is DataContentRequest)
            {
                terminalManager.HandleDataContentRequest((DataContentRequest)message);
            }
            else if (message is ChangeAuthModeAck)
            {
                terminalManager.HandleModeChangeResult((ChangeAuthModeAck)message);
            }
            else if (message is CancelAuthRequest)
            {
                terminalManager.HandleCancelAuth((CancelAuthRequest)message);
            }
            else if (message is QueryGrayRecordRequest)
            {
                terminalManager.HandleQueryGrayRecord((QueryGrayRecordRequest)message);
            }
            else if (message is null)
            {
                terminalManager.HandleFakeNullMessage();
            }
        }

        private void SetupTerminalManagers()
        {
            foreach (var ptHandler in paymentTerminalHandlers)
            {
                foreach (var pumpId in ptHandler.AssociatedPumpIds)
                {
                    var mgr = new TerminalManager(this, pumpId, ptHandler.GetSubAddressForPump(pumpId), spsManager, ptHandler, interval, this.CardAppType);
                    terminalManagers.Add(mgr);
                    InfoLog($"Setting up TerminalManager, Pump Id: {mgr.PumpId}, Sub address: {mgr.SubAddress}");
                }
            }
        }

        private void InitPumpPriceDict()
        {
            foreach (var ptHandler in paymentTerminalHandlers)
            {
                foreach (var p in ptHandler.AssociatedPumpIds)
                {
                    if (!pumpPriceDict.ContainsKey(p))
                    {
                        pumpPriceDict.Add(p, false);
                    }
                }
            }
        }

        private void SetupTransactionSubmitters()
        {
            foreach (var ptHandler in paymentTerminalHandlers)
            {
                foreach (var p in ptHandler.AssociatedPumpIds)
                {
                    if (submitters.Any(s => s.Id == p))
                        continue;

                    var s = new TrxSubmitter(p, cloudCredential);
                    submitters.Add(s);

                    InfoLog($"Setting up Transaction submitter,  Pump Id: {p}");
                }
            }
        }

        private TrxSubmitter GetSubmitter(int pumpId)
        {
            return submitters.FirstOrDefault(s => s.Id == pumpId);
        }

        private void SetFuelProductMapping()
        {
            if (!string.IsNullOrEmpty(fuelProductMapping))
            {
                var sequence = fuelProductMapping.Split(';')
                    .Select(s => s.Split(':'))
                    .Select(a => new { Barcode = int.Parse(a[0]), FuelNo = int.Parse(a[1]) });

                foreach (var pair in sequence)
                {
                    if (!fuelMappingDict.ContainsKey(pair.Barcode))
                    {
                        fuelMappingDict.Add(pair.Barcode, pair.FuelNo);
                    }
                }
            }
        }

        private int GetFuelNo(int barcode)
        {
            if (fuelMappingDict.ContainsKey(barcode))
                return fuelMappingDict[barcode];

            return -1;
        }

        public int GetBarcode(int fuelNo)
        {
            return fuelMappingDict.FirstOrDefault(x => x.Value == fuelNo).Key;
        }

        private void InitFuelName()
        {
            fuelNameDict.Clear();

            var fuelNames = spsManager.GetFuelNames();

            foreach (var item in fuelNameDict)
            {
                int barcode = GetBarcode(Convert.ToInt32(item.Key));
                if (!fuelNameDict.ContainsKey(barcode))
                {
                    fuelNameDict.Add(barcode, item.Value);
                }
            }
        }

        private void InitFuelPrices()
        {
            fuelCodePriceDict.Clear();
            var fuelPrices = spsManager.GetCurrentFuelPrices();
            foreach (var item in fuelPrices)
            {
                if (!fuelCodePriceDict.ContainsKey(item.Key))
                {
                    fuelCodePriceDict.Add(item.Key, item.Value);
                }
            }
        }

        public string GetFuelName(int barcode)
        {
            string fuelName = string.Empty;
            fuelNameDict.TryGetValue(barcode, out fuelName);
            return fuelName;
        }

        private TerminalManager GetTerminalManager(int pumpId)
        {
            return terminalManagers.FirstOrDefault(t => t.PumpId == pumpId);
        }

        private TerminalManager GetTerminalManager(string identifier, int pumpId)
        {
            return terminalManagers.FirstOrDefault(t => t.Identifier == identifier && t.SubAddress == pumpId);
        }

        private void InfoLog(string info)
        {
            logger.Info(info);
        }

        private void DebugLog(string debugMsg)
        {
            logger.Debug(debugMsg);
        }

        #endregion

        #region Properties

        public FdcServerHostApp FdcServer
        {
            get { return fdcServerApp; }
        }

        public SpsDataCourierApp SpsDataCourier => spsDataCourierApp;

        public int PosId { get; private set; }

        public long CurrentShiftNo
        {
            get { return initalData.ShiftNo; }
        }

        public DiscountServiceClient DiscountServiceClient { get; }

        public Dictionary<string, uint> CurrentFuelPrices => fuelCodePriceDict;

        public StationInfo StationInfo { get; private set; }

        public int ReservedBalance => reservedBalance;

        #endregion
    }

    #region Config parameters

    public class PosAppContructorParameterV1
    {
        public int PosId { get; set; }

        public string Username { get; set; }

        public string Password { get; set; }
        
        public string AuthServiceBaseUrl { get; set; }
        
        public string TransactionServiceBaseUrl { get; set; }
        
        public string DiscountServiceBaseUrl { get; set; }
        
        public bool OnlineDiscount { get; set; }

        public int DiscountTimeout { get; set; }
        
        public string DeviceSN { get; set; }
        
        public string PromoCategories { get; set; }
        
        public List<FuelMappingV1> FuelMappingArr { get; set; }
        
        public int CardAppType { get; set; }

        public int ReservedBalance { get; set; }

        public int PumpMode { get; set; }
        
        public int Interval { get; set; }
        
        public SpsDbConnectionSetting ConnectionString { get; set; }

        public int StationNo { get; set; }

        public string StationName { get; set; }
    }

    public class FuelMappingV1
    {
        public int Barcode { get; set; }

        public string FuelNo { get; set; }
    }

    public class SpsDbConnectionSetting
    {
        public string Server { get; set; }

        public int Port { get; set; }

        public string Username { get; set; }

        public string Password { get; set; }
    }

    #endregion
}