using Dfs.WayneChina.SpsDataCourier.Guard;
using Dfs.WayneChina.SpsDataCourier.Models;
using Dfs.WayneChina.SpsDataCourier.SpsData;
using Edge.Core.Processor;
using Edge.Core.Processor.Dispatcher.Attributes;
using Microsoft.EntityFrameworkCore;
using NLog.LayoutRenderers;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Dfs.WayneChina.SpsDataCourier
{
    /// <summary>
    /// Application to upload account/card/transaction/card recharge and reductions.
    /// </summary>
    [MetaPartsDescriptor(
    "lang-zh-cn:Sps传输服务 Applang-en-us:Sps Data Courier",
    "lang-zh-cn:用于传输SPS数据库中的数据到远端服务器" +
    "lang-en-us:Used for sending SPS data to remote host",
        new[] { "lang-zh-cn:数据上传服务lang-en-us:Data Upload Service" })]
    public class SpsDataCourierApp : IAppProcessor
    {
        #region Fields

        public string SerialNumber { get; set; }
        public string Name { get; set; }
        private string username;
        private string password;
        private string authServiceBaseUrl;
        private string accountServiceBaseUrl;
        private string accountServiceRelativeUrl;
        private string deviceSN;

        public int Id { get; }
        public object OfflineAccount { get; private set; }
        public string MetaConfigName { get; set; }

        private DbMonitor dbMonitor;
        private DataCourier dataCourier;
        private int scanInterval;
        private string fuelMapping;
        private bool smartFuel05Compatible;
        private int retryCount;
        private bool enableSync;
        private bool excludeCurrentSite;
        private int syncInterval;
        private string checkVersionRelativeUrl;
        private string syncDataRelativeUrl;

        private ManualResetEvent mre = new ManualResetEvent(false);

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

        private Downloader downloader;
        private readonly ConnectionInfo connectionInfo;
        #endregion

        #region Logger

        static NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("SpsDataCourier");

        #endregion

        private string _mySqlConn;

        #region Constructor

        [ParamsJsonSchemas("appCtorParamsJsonSchema")]
        public SpsDataCourierApp(DataCourierAppContructorParameterV1 config)
        {
            this.Id = config.Id;
            this.username = config.Username;
            this.password = config.Password;
            this.authServiceBaseUrl = config.AuthServiceBaseUrl;
            this.accountServiceBaseUrl = config.AccountServiceBaseUrl;
            this.accountServiceRelativeUrl = config.AccountServiceRelativeUrl;

            this.deviceSN = config.DeviceSN;
            this.scanInterval = config.ScanInterval;
            this.fuelMapping = string.Join(";", config.FuelMappingArr.Select(m => $"{m.Barcode}:{m.FuelNo}"));
            this.retryCount = config.RetryCount;
            this.enableSync = config.EnableSync;
            this.excludeCurrentSite = config.ExcludeCurrentSite;
            this.syncInterval = config.SyncInterval;
            this.checkVersionRelativeUrl = config.CheckVersionRelativeUrl;
            this.syncDataRelativeUrl = config.SyncDataRelativeUrl;

            _mySqlConn = FormatConnectionString(config.ConnectionString);
            SetFuelProductMapping();

            connectionInfo = new ConnectionInfo
            {
                AccountServiceBaseUrl = accountServiceBaseUrl,
                AccountServiceRelativeUrl = accountServiceRelativeUrl,
                AuthServiceBaseUrl = authServiceBaseUrl,
                DeviceSN = deviceSN,
                UserName = username,
                Password = password
            };
        }

        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;";
        }

        public SpsDataCourierApp(int id, string username, string password, string authServiceBaseUrl, string accountServiceBaseUrl,
            string accountServiceRelativeUrl, string deviceSN, int scanInterval, string fuelMapping, string smartFuel05Compatible,
            int retryCount, int enableSync, int excludeCurrentSite, int syncInterval, string checkVersionRelativeUrl, string syncDataRelativeUrl, string connectionString)
        {
            Id = id;
            this.username = username;
            this.password = password;
            this.authServiceBaseUrl = authServiceBaseUrl;
            this.accountServiceBaseUrl = accountServiceBaseUrl;
            this.accountServiceRelativeUrl = accountServiceRelativeUrl;
            this.deviceSN = deviceSN;
            this.scanInterval = scanInterval;
            this.fuelMapping = fuelMapping;
            this.smartFuel05Compatible = Convert.ToBoolean(smartFuel05Compatible);
            this.retryCount = retryCount;
            this.enableSync = enableSync > 0 ? true : false;
            this.excludeCurrentSite = excludeCurrentSite > 0 ? true : false;
            this.syncInterval = syncInterval;
            this.checkVersionRelativeUrl = checkVersionRelativeUrl;
            this.syncDataRelativeUrl = syncDataRelativeUrl;

            _mySqlConn = connectionString;
            SetFuelProductMapping();

            connectionInfo = new ConnectionInfo
            {
                AccountServiceBaseUrl = accountServiceBaseUrl,
                AccountServiceRelativeUrl = accountServiceRelativeUrl,
                AuthServiceBaseUrl = authServiceBaseUrl,
                DeviceSN = deviceSN,
                UserName = username,
                Password = password
            };
        }

        #endregion

        #region Fuel barcode and fuel number

        private void SetFuelProductMapping()
        {
            if (!string.IsNullOrEmpty(fuelMapping))
            {
                var sequence = fuelMapping.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;
        }

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

        #endregion

        #region IApplication implementation, Init

        public void Init(IEnumerable<IProcessor> processors)
        {
            dbMonitor = new DbMonitor(scanInterval, _mySqlConn, smartFuel05Compatible);
            dataCourier =
                new DataCourier(username, password, authServiceBaseUrl, accountServiceBaseUrl, accountServiceRelativeUrl, deviceSN, retryCount);

            if (enableSync)
            {
                downloader = new Downloader(connectionInfo, dbMonitor, _mySqlConn, syncInterval, checkVersionRelativeUrl, 
                    syncDataRelativeUrl, excludeCurrentSite);
            }
        }

        #endregion

        #region Start

        public Task<bool> Start()
        {
            // Hook up the events.
            dbMonitor.OnCardTrxCreated += DbMonitor_OnCardTrxCreated;

            dbMonitor.OnRechargeCreated += DbMonitor_OnRechargeCreated;

            dbMonitor.OnAccountCreated += DbMonitor_OnAccountCreated;
            dbMonitor.OnAccountUpdated += DbMonitor_OnAccountUpdated;

            dbMonitor.OnCardInfoCreated += DbMonitor_OnCardInfoCreated;
            dbMonitor.OnCardInfoUpdated += DbMonitor_OnCardInfoUpdated;
            dbMonitor.OnCardInfoDeleted += DbMonitor_OnCardInfoDeleted;

            // cardreploss
            dbMonitor.OnLostCardCreated += DbMonitor_OnLostCardCreated;

            // cardlogout
            dbMonitor.OnClosedCardCreated += DbMonitor_OnClosedCardCreated;

            // base black card
            dbMonitor.OnBaseBlackCardCreated += DbMonitor_OnBaseBlackCardCreated;
            dbMonitor.OnBaseBlackCardDeleted += DbMonitor_OnBaseBlackCardDeleted;

            // add black card
            dbMonitor.OnBlacklistedCardCreated += DbMonitor_OnBlacklistedCardCreated;
            dbMonitor.OnBlacklistedCardUpdated += DbMonitor_OnBlacklistedCardUpdated;
            dbMonitor.OnBlacklistedCardDeleted += DbMonitor_OnBlacklistedCardDeleted;

            // delete black card
            dbMonitor.OnReleasedCardCreated += DbMonitor_OnReleasedCardCreated;
            dbMonitor.OnReleasedCardDeleted += DbMonitor_OnReleasedCardDeleted;

            dbMonitor.OnGrayInfoCreated += DbMonitor_OnGrayInfoCreated;
            dbMonitor.OnGrayInfoDeleted += DbMonitor_OnGrayInfoDeleted;

            dbMonitor.OnMultipleAccountsCreated += DbMonitor_OnMultipleAccountsCreated;
            dbMonitor.OnMultipleAccountsUpdated += DbMonitor_OnMultipleAccountsUpdated;
            dbMonitor.OnMultipleCardsCreated += DbMonitor_OnMultipleCardsCreated;
            dbMonitor.OnMultipleCardsUpdated += DbMonitor_OnMultipleCardsUpdated;

            // Migrate database
            using (var context = new GuardDbContext())
            {
                context.Database.Migrate();
            }

            ThreadPool.QueueUserWorkItem(arg =>
            {
                StartDbMonitor();

                if (downloader != null)
                    downloader.Start();
            });

            ThreadPool.QueueUserWorkItem(arg =>
            {
                UploadExistingData();
            });

            return Task.FromResult(true);
        }

        #endregion

        #region Stop

        public Task<bool> Stop()
        {
            if (dbMonitor != null)
            {
                // Unhook the events.
                dbMonitor.OnCardTrxCreated -= DbMonitor_OnCardTrxCreated;

                dbMonitor.OnRechargeCreated -= DbMonitor_OnRechargeCreated;

                dbMonitor.OnAccountCreated -= DbMonitor_OnAccountCreated;
                dbMonitor.OnAccountUpdated -= DbMonitor_OnAccountUpdated;

                dbMonitor.OnCardInfoCreated -= DbMonitor_OnCardInfoCreated;
                dbMonitor.OnCardInfoUpdated -= DbMonitor_OnCardInfoUpdated;
                dbMonitor.OnCardInfoDeleted -= DbMonitor_OnCardInfoDeleted;

                // cardreploss
                dbMonitor.OnLostCardCreated -= DbMonitor_OnLostCardCreated;

                // cardlogout
                dbMonitor.OnClosedCardCreated -= DbMonitor_OnClosedCardCreated;

                // base black card
                dbMonitor.OnBaseBlackCardCreated -= DbMonitor_OnBaseBlackCardCreated;
                dbMonitor.OnBaseBlackCardDeleted -= DbMonitor_OnBaseBlackCardDeleted;

                // add black card
                dbMonitor.OnBlacklistedCardCreated -= DbMonitor_OnBlacklistedCardCreated;
                dbMonitor.OnBlacklistedCardUpdated -= DbMonitor_OnBlacklistedCardUpdated;
                dbMonitor.OnBlacklistedCardDeleted -= DbMonitor_OnBlacklistedCardDeleted;

                // delete black card
                dbMonitor.OnReleasedCardCreated -= DbMonitor_OnReleasedCardCreated;
                dbMonitor.OnReleasedCardDeleted -= DbMonitor_OnReleasedCardDeleted;

                dbMonitor.OnGrayInfoCreated -= DbMonitor_OnGrayInfoCreated;
                dbMonitor.OnGrayInfoDeleted -= DbMonitor_OnGrayInfoDeleted;

                dbMonitor.OnMultipleAccountsCreated -= DbMonitor_OnMultipleAccountsCreated;
                dbMonitor.OnMultipleAccountsUpdated -= DbMonitor_OnMultipleAccountsUpdated;
                dbMonitor.OnMultipleCardsCreated -= DbMonitor_OnMultipleCardsCreated;
                dbMonitor.OnMultipleCardsUpdated -= DbMonitor_OnMultipleCardsUpdated;


                dbMonitor.Stop();
            }

            return Task.FromResult(true);
        }

        #endregion

        #region Event handlers

        #region OnAccountCreated

        // New account created
        private void DbMonitor_OnAccountCreated(object sender, EntryFoundEventArgs e)
        {
            if (e.TableAudit.AccountCreated.HasValue && e.TableAudit.AccountCreated.Value != 0)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = context.TAcctinfo.FirstOrDefault(a => a.Gid == Convert.ToUInt64(e.TableAudit.AccountCreated.Value));

                    if (record != null)
                    {
                        var result = SubmitAccountRecordAsync(record, HostOperationType.CreateAccount).Result;

                        logger.Info($"Just try to submit a new Account for AccoutId: {record.AcctId}, GID: {record.Gid}, Success? {result.Success}");

                        if (result.Success)
                        {
                            context.TTableaudit.Remove(e.TableAudit);
                            var val = context.SaveChanges();
                        }
                        else
                        {
                            //In case that host returns failure, should retry later on.
                            logger.Warn("Result not success, don't remove the Created account record");
                            //context.TTableaudit.Remove(e.TableAudit);
                            //var val = context.SaveChanges();
                        }
                    }
                    else
                    {
                        logger.Error("Stange, could not find the record for created account!");

                        context.TTableaudit.Remove(e.TableAudit);
                        context.SaveChanges();
                    }
                }
            }
        }

        #endregion

        #region OnAccountUpdated

        //Account updated
        private async void DbMonitor_OnAccountUpdated(object sender, EntryFoundEventArgs e)
        {
            if (e.TableAudit.AccountUpdated.HasValue && e.TableAudit.AccountUpdated.Value != 0)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = await
                        context.TAcctinfo.FirstOrDefaultAsync(a => a.Gid == Convert.ToUInt64(e.TableAudit.AccountUpdated.Value));

                    if (record != null)
                    {
                        var result = SubmitAccountRecordAsync(record, HostOperationType.UpdateAccount).Result;
                        logger.Info($"Update account info, AccountId: {record.AcctId}, Name: {record.BelongTo}, Result: {result.Success}");

                        if (result.Success)
                        {
                            CleanupRecord(context, e.TableAudit);
                        }
                        else
                        {
                            // Upload failed, should retry later on.
                            logger.Warn("Result not success, shall retry uploading the Updated Account record");
                        }
                    }
                    else
                    {
                        logger.Error("Could not find the Updated Account record");

                        // Makes no sense to keep it.
                        CleanupRecord(context, e.TableAudit);
                    }
                }
            }
        }

        #endregion

        #region Record cleanup

        private int CleanupRecord(SpsDbContext context, TTableaudit entity)
        {
            logger.Info("Cleaning up...");

            try
            {
                context.TTableaudit.Remove(entity);
                return context.SaveChanges();
            }
            catch (Exception ex)
            {
                logger.Error(ex.ToString());
            }

            return 0;
        }

        #endregion

        #region CardInfoCreated

        //New card issued (card info created)
        private void DbMonitor_OnCardInfoCreated(object sender, EntryFoundEventArgs e)
        {
            if (e.TableAudit.CardInfoCreated.HasValue && e.TableAudit.CardInfoCreated.Value != 0)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = context.TCardinfo.FirstOrDefault(c => c.Gid == Convert.ToUInt64(e.TableAudit.CardInfoCreated.Value));
                    if (record != null)
                    {
                        var result = SubmitCardInfoAsync(record, HostOperationType.CreateCard).Result;

                        logger.Info($"Submit a new CardInfo for CardNo: {record.CardNo}, AccountId: {record.AcctId} Success? {result.Success}");

                        if (result.Success)
                        {
                            CleanupRecord(context, e.TableAudit);
                        }
                        else
                        {
                            // Retry later on when host returns failure.
                            logger.Warn("Result not success, shall retry uploading Created Card Info");
                            
                            if (result.Code == 4)
                            {
                                HandleMissingAccount(context, record.AcctId);
                            }
                        }
                    }
                    else
                    {
                        logger.Error("That's strange, a new CardInfo created, but I can't find it in the table...");

                        // Makes no sense to keep it here.
                        CleanupRecord(context, e.TableAudit);
                    }
                }
            }
        }

        #endregion

        #region CardInfoUpdated

        //Card info updated (could be personal inforation updated)
        private async void DbMonitor_OnCardInfoUpdated(object sender, EntryFoundEventArgs e)
        {
            if (e.TableAudit.CardInfoUpdated.HasValue && e.TableAudit.CardInfoUpdated.Value != 0)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = await context.TCardinfo.FirstOrDefaultAsync(c => c.Gid == Convert.ToUInt64(e.TableAudit.CardInfoUpdated.Value));

                    if (record != null)
                    {
                        // Find if there is a closed (deactivated) card.
                        var closedCard = await context.TCardlogout
                            .FirstOrDefaultAsync(cl => cl.AcctId == record.AcctId && cl.Holder == record.Holder);

                        if (closedCard != null)
                        {
                            if (closedCard.LgtDate.HasValue && (DateTime.Now - closedCard.LgtDate.Value) < TimeSpan.FromHours(24))
                            {
                                //Assume the card was replaced!
                                var result = SubmitCardInfoAsync(record, HostOperationType.CreateCard).Result;

                                logger.Info($"A card closed and a new card replacement created, " +
                                    $"CardNo: {record.CardNo}, Balance: {record.Money}, Result: {result.Success}");

                                if (result.Success)
                                {
                                    CleanupRecord(context, e.TableAudit);
                                }
                                else
                                {
                                    logger.Warn("Result not success, should try again on Updated Card Info");
                                }
                            }
                            else
                            {
                                var result = SubmitCardInfoAsync(record, HostOperationType.UpdateCard).Result;
                                logger.Info($"Update card info, CardNo: {record.CardNo}, Balance: {record.Money}, PreMalloc: {record.PreMalloc}, Result: {result.Success}");

                                if (result.Success)
                                {
                                    CleanupRecord(context, e.TableAudit);
                                }
                                else
                                {
                                    logger.Warn("Result not success, should try again on Updated Card Info");
                                }
                            }
                        }
                        else
                        {
                            var result = SubmitCardInfoAsync(record, HostOperationType.UpdateCard).Result;
                            logger.Info($"Update card info, CardNo: {record.CardNo}, Balance: {record.Money}, Result: {result.Success}");

                            if (result.Success)
                            {
                                CleanupRecord(context, e.TableAudit);
                            }
                            else
                            {
                                logger.Warn("Result not success, should try again on Updated Card Info");
                            }
                        }
                    }
                    else
                    {
                        logger.Error("*** Could not find the card info record");

                        CleanupRecord(context, e.TableAudit);
                    }
                }
            }
        }

        #endregion

        #region CardInfoDeleted

        //Card info deleted (could be balance updated or card closed)
        private async void DbMonitor_OnCardInfoDeleted(object sender, CardChangedEventArgs e)
        {
            if (!string.IsNullOrEmpty(e.CardNo))
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = await context.TCardinfo
                        .OrderBy(r => r.CardNo)
                        .LastOrDefaultAsync(c => c.CardNo == e.CardNo);

                    if (record != null)
                    {
                        logger.Info("Card info deleted, what can we do about it?");
                    }
                    else
                    {
                        logger.Info($"*** The card of CardNo: {e.CardNo} must be closed/deactivated.");
                    }
                }

                if (e.TableAudit == null)
                {
                    logger.Error("TableAudit is null");
                }

                using (var context = new SpsDbContext(_mySqlConn))
                {
                    context.Attach(e.TableAudit);
                    context.TTableaudit.Remove(e.TableAudit);
                    context.SaveChanges();
                }
            }
        }

        #endregion

        #region CardTrxCreated

        private void DbMonitor_OnCardTrxCreated(object sender, CardTrxEventArgs e)
        {
            if (e.TableAudit.TransCreated.HasValue)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    FuelProductInfo fuelProduct = new FuelProductInfo();

                    var trans = context.TTrdinfo.FirstOrDefault(t => t.Gid == Convert.ToUInt64(e.TableAudit.TransCreated.Value));

                    if (trans != null)
                    {
                        TTrdinfo grayTrade = null;
                        //In case it's ungray trade.
                        if (trans.TrdType == 2)
                        {
                            grayTrade = context.TTrdinfo
                                .FirstOrDefault(t => t.TrdType == 1 && t.Ctc == trans.Ctc && t.Ttctime == trans.Ttctime);
                        }

                        var fuel = context.TFuellist.FirstOrDefault(f => f.FuelNo == trans.CommId);

                        if (fuel != null)
                        {
                            fuelProduct.FuelName = fuel.Name;
                            fuelProduct.Barcode = GetBarcode(Convert.ToInt32(fuel.FuelNo)).ToString();
                        }
                        else
                        {
                            if (grayTrade != null)
                            {
                                fuel = context.TFuellist.FirstOrDefault(f => f.FuelNo == grayTrade.CommId);
                                if (fuel != null)
                                {
                                    fuelProduct.FuelName = fuel.Name;
                                    fuelProduct.Barcode = GetBarcode(Convert.ToInt32(fuel.FuelNo)).ToString();
                                    fuelProduct.Price = grayTrade.Prc.Value;
                                }
                            }
                        }

                        if (trans.TrdType == 10 || trans.TrdType == 11 || trans.TrdType == 8)
                        {
                            logger.Error($"Fuel change record or bank card payment record or fuel price download record, trdType: {trans.TrdType}, should be ignored");
                            CleanupRecord(context, e.TableAudit);

                            return;
                        }

                        var result = SubmitCardTrxRecordAsync(trans, grayTrade, fuelProduct).Result;

                        logger.Info($"Just submit a new card payment record for " +
                            $"CardNo {trans.CardNo}, TTCTime: {trans.Ttctime}, GID: {trans.Gid}, Success? {result.Success}");

                        if (result.Success)
                        {
                            dbMonitor.AddToProcessedQueue(Convert.ToInt64(e.TableAudit.TransCreated.Value));

                            try
                            {
                                var val = CleanupRecord(context, e.TableAudit);
                                var spsDbMonitor = sender as DbMonitor;
                                if (spsDbMonitor != null)
                                {
                                    spsDbMonitor.ClearFromQueue(e.TableAudit);
                                }
                                logger.Info($"Removing the Trx record from table, result: {val}");
                            }
                            catch (Exception ex)
                            {
                                logger.Error(ex);
                            }
                        }
                        else
                        {
                            var spsDbMonitor = sender as DbMonitor;
                            
                            if (spsDbMonitor != null)
                            {
                                spsDbMonitor.ClearFromQueue(e.TableAudit);
                            }

                            logger.Warn("Upload result not success, should try again on New Card Transaction later");

                            if (result.Code == 3) // missing card
                            {
                                HandleMissingCard(context, trans.CardNo);
                            }
                            else if (result.Code == 4) // missing account
                            {
                                var account = context.TCardinfo.FirstOrDefault(c => c.CardNo == trans.CardNo);
                                if (account != null)
                                {
                                    HandleMissingAccount(context, account.AcctId);
                                }
                            }
                        }
                    }
                }
            }
        }

        #endregion

        #region Recharge and reduction created

        private void DbMonitor_OnRechargeCreated(object sender, EntryFoundEventArgs e)
        {
            if (e.TableAudit.RechargeCreated.HasValue && e.TableAudit.RechargeCreated.Value != 0)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = context.TRechdebitRep.FirstOrDefault(r => r.Gid == Convert.ToUInt64(e.TableAudit.RechargeCreated.Value));

                    if (record != null)
                    {
                        var result = SubmitRechargeReductionRecord(record).Result;

                        logger.Info($"Just try to submit a new recharge/reduction reocrd for " +
                            $"CardNo: {record.CardNo}, Amount: {record.Mon}, GID = {record.Gid}, Success? {result.Success}");

                        if (result.Success)
                        {
                            CleanupRecord(context, e.TableAudit);
                        }
                        else
                        {
                            if (result.Code == 3)
                            {
                                logger.Info("Missing card, try to upload it first");
                                HandleMissingCard(context, record.CardNo);
                            }
                            else if (result.Code == 4)
                            {
                                logger.Info("Missing account, try to upload it first");
                                HandleMissingAccount(context, record.AcctId);
                            }

                            logger.Warn("Upload result not success, should try again on Recharge/Reduction later");
                        }
                    }
                }
            }
        }

        #endregion

        #region Lost card created

        //挂失卡或者解挂卡
        private void DbMonitor_OnLostCardCreated(object sender, EntryFoundEventArgs e)
        {
            logger.Info($"New report of lost card or card released");

            if (e.TableAudit.LostCardCreated.HasValue && e.TableAudit.LostCardCreated.Value != 0)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = context.TCardreploss.FirstOrDefault(r => r.Gid == Convert.ToUInt64(e.TableAudit.LostCardCreated.Value));
                    if (record != null)
                    {
                        var cardRepLossResult = SubmitCardRepLossAsync(record).Result;
                        logger.Info($"Just try to submit a new CardRepLoss {record.CardNo}, GID = {record.Gid}, Success? {cardRepLossResult.Success}");

                        var result = SubmitCardOperationInfoAsync(record, string.Empty).Result;

                        logger.Info($"Just try to submit a new lost or released card for CardNo: {record.CardNo}, GID = {record.Gid}, Success? {result.Success}");

                        if (result.Success && cardRepLossResult.Success)
                        {
                            CleanupRecord(context, e.TableAudit);
                        }
                        else
                        {
                            logger.Warn("Upload result not success, should try again on Lost Card later");
                        }
                    }
                }
            }
        }

        #endregion

        #region 注销卡 Closed card created

        //注销卡
        private void DbMonitor_OnClosedCardCreated(object sender, EntryFoundEventArgs e)
        {
            logger.Info($"New closed/deactivated card");

            if (e.TableAudit.ClosedCardCreated.HasValue && e.TableAudit.ClosedCardCreated.Value != 0)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = context.TCardlogout.FirstOrDefault(r => r.Gid == Convert.ToUInt64(e.TableAudit.ClosedCardCreated.Value));
                    if (record != null)
                    {
                        var result = SubmitCardOperationInfoAsync(null, record.CardNo).Result;

                        logger.Info($"Just try to submit a new closed card for CardNo: {record.CardNo}, GID = {record.Gid}, Success? {result.Success}");

                        if (result.Success)
                        {
                            CleanupRecord(context, e.TableAudit);
                        }
                        else
                        {
                            logger.Warn("Upload not success, should try again on Closed/Deactivated card later");
                        }
                    }
                }
            }
        }

        #endregion

        #region 基础黑名单 (t_blackcard)

        private void DbMonitor_OnBaseBlackCardCreated(object sender, EntryFoundEventArgs e)
        {
            logger.Info("Handling BlackCard record [Create]");

            if (e.TableAudit != null && e.TableAudit.BaseBlackCardCreated.HasValue)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = context.TBlackcard
                        .FirstOrDefault(r => r.Gid == Convert.ToUInt64(e.TableAudit.BaseBlackCardCreated.Value));

                    if (record != null)
                    {
                        var listedCardInfo = new ListedCardInfo();
                        listedCardInfo.ListedType = ListedType.Base; // base black card
                        listedCardInfo.Gid = Convert.ToInt64(record.Gid);
                        listedCardInfo.CardNo = record.CardNo;
                        listedCardInfo.ListedDate =
                            record.BlackDate == null ? DateTime.MinValue : record.BlackDate.Value;
                        listedCardInfo.AccountGid = Convert.ToInt64(record.AcctGid);
                        listedCardInfo.AccountId = record.AcctId;
                        listedCardInfo.CardType = Convert.ToByte(record.CardType);
                        listedCardInfo.DiscountNo =
                            record.DiscountNo == null ? 0 : record.DiscountNo.Value;
                        listedCardInfo.Reason = record.Reason;
                        listedCardInfo.UploadFlag = Convert.ToByte(record.UploadFlag);
                        listedCardInfo.OperationType = 0; // Create

                        var result = SendListedCardAsync(listedCardInfo).Result;

                        if (result.Success)
                        {
                            logger.Info($"Upload Base BlackCard record [Create] {record.CardNo}, success");
                            CleanupRecord(context, e.TableAudit);
                        }
                        else
                        {
                            logger.Warn("Upload not successful, should try again later on Base BlackCard [CREATE]");
                        }
                    }
                    else
                    {
                        logger.Warn($"Base BlackCard [Create], record gid: {e.TableAudit.BaseBlackCardCreated.Value} not found!");
                        CleanupRecord(context, e.TableAudit);
                    }
                }
            }
        }

        private void DbMonitor_OnBaseBlackCardDeleted(object sender, CardChangedEventArgs e)
        {
            if (e.TableAudit != null && !string.IsNullOrEmpty(e.TableAudit.BaseBlackCardDeleted))
            {
                logger.Info($"Handling BlackCard record [Delete], original Card No: {e.TableAudit.BaseBlackCardDeleted}");

                var listedCard = new ListedCardInfo();
                listedCard.CardNo = e.TableAudit.BaseBlackCardDeleted;
                listedCard.ListedType = ListedType.Base; // base black card
                listedCard.OperationType = 2; //delete

                var result = SendListedCardAsync(listedCard).Result;
                if (result.Success)
                {
                    logger.Info($"Upload Base BlackCard [Deleted], Card No: {e.TableAudit.BaseBlackCardDeleted}, success");
                    using (var context = new SpsDbContext(_mySqlConn))
                    {
                        CleanupRecord(context, e.TableAudit);
                    }
                }
                else
                {
                    logger.Info($"Upload Base BlackCard [Deleted], Card No: {e.TableAudit.BaseBlackCardDeleted} failed, should try again later");
                }
            }
        }

        #endregion

        #region 增量黑名单卡 (t_addblackcard)

        //增量黑名单卡 - Created
        private void DbMonitor_OnBlacklistedCardCreated(object sender, EntryFoundEventArgs e)
        {
            logger.Info("Handling AddBlackCard record [Create]");

            if (e.TableAudit != null && e.TableAudit.BlacklistedCardCreated.HasValue)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = context.TAddblackcard
                        .FirstOrDefault(r => r.Gid == Convert.ToUInt64(e.TableAudit.BlacklistedCardCreated.Value));

                    if (record != null)
                    {
                        var listedCardInfo = new ListedCardInfo();
                        listedCardInfo.ListedType = ListedType.Add; // add black card
                        listedCardInfo.Gid = Convert.ToInt64(record.Gid);
                        listedCardInfo.CardNo = record.CardNo;
                        listedCardInfo.ListedDate =
                            record.BlackDate == null ? DateTime.MinValue : record.BlackDate.Value;
                        listedCardInfo.AccountGid = Convert.ToInt64(record.AcctGid);
                        listedCardInfo.AccountId = record.AcctId;
                        listedCardInfo.CardType = Convert.ToByte(record.CardType);
                        listedCardInfo.DiscountNo =
                            record.DiscountNo == null ? 0 : record.DiscountNo.Value;
                        listedCardInfo.Reason = record.Reason;
                        listedCardInfo.UploadFlag = Convert.ToByte(record.UploadFlag);
                        listedCardInfo.OperationType = 0; // Create

                        var result = SendListedCardAsync(listedCardInfo).Result;

                        if (result.Success)
                        {
                            logger.Info($"Upload AddBlackCard record [Create] {record.CardNo}, success");
                            CleanupRecord(context, e.TableAudit);
                        }
                        else
                        {
                            logger.Warn("Upload not successful, should try again later on AddBlackCard [CREATE]");
                        }
                    }
                    else
                    {
                        logger.Warn($"AddBlackCard [Create], record gid: {e.TableAudit.BlacklistedCardUpdated.Value} not found!");
                        CleanupRecord(context, e.TableAudit);
                    }
                }
            }
        }

        //增量黑名单卡 - Updated
        private void DbMonitor_OnBlacklistedCardUpdated(object sender, EntryFoundEventArgs e)
        {
            logger.Info($"Handling AddBlackCard record [Update]");

            if (e.TableAudit != null && e.TableAudit.BlacklistedCardUpdated.HasValue)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = context.TAddblackcard
                        .FirstOrDefault(r => r.Gid == Convert.ToUInt64(e.TableAudit.BlacklistedCardUpdated.Value));

                    if (record != null)
                    {
                        var listedCardInfo = new ListedCardInfo();
                        listedCardInfo.ListedType = ListedType.Add; // add black card
                        listedCardInfo.Gid = Convert.ToInt64(record.Gid);
                        listedCardInfo.CardNo = record.CardNo;
                        listedCardInfo.ListedDate =
                            record.BlackDate == null ? DateTime.MinValue : record.BlackDate.Value;
                        listedCardInfo.AccountGid = Convert.ToInt64(record.AcctGid);
                        listedCardInfo.AccountId = record.AcctId;
                        listedCardInfo.CardType = Convert.ToByte(record.CardType);
                        listedCardInfo.DiscountNo =
                            record.DiscountNo == null ? 0 : record.DiscountNo.Value;
                        listedCardInfo.Reason = record.Reason;
                        listedCardInfo.UploadFlag = Convert.ToByte(record.UploadFlag);
                        listedCardInfo.OperationType = 1; // Update

                        var result = SendListedCardAsync(listedCardInfo).Result;

                        if (result.Success)
                        {
                            logger.Info($"Upload AddBlackCard record [Update] {record.CardNo}, success");
                            CleanupRecord(context, e.TableAudit);
                        }
                        else
                        {
                            logger.Warn("Upload not successful, should try again later on AddBlackCard [UPDATE]");
                        }
                    }
                    else
                    {
                        logger.Warn($"AddBlackCard [Update], record gid: {e.TableAudit.BlacklistedCardUpdated.Value} not found!");
                        CleanupRecord(context, e.TableAudit);
                    }
                }
            }
        }

        //增量黑名单卡 - Deleted
        private void DbMonitor_OnBlacklistedCardDeleted(object sender, CardChangedEventArgs e)
        {
            if (e.TableAudit != null && !string.IsNullOrEmpty(e.TableAudit.BlacklistedCardDeleted))
            {
                logger.Info($"Handling AddBlackCard record [Delete], original Card No: {e.TableAudit.BlacklistedCardDeleted}");

                var listedCard = new ListedCardInfo();
                listedCard.CardNo = e.TableAudit.BlacklistedCardDeleted;
                listedCard.ListedType = ListedType.Add; // add black card
                listedCard.OperationType = 2; //delete

                var result = SendListedCardAsync(listedCard).Result;
                if (result.Success)
                {
                    logger.Info($"Upload AddBlackCard [Deleted], Card No: {e.TableAudit.BlacklistedCardDeleted}, success");
                    using (var context = new SpsDbContext(_mySqlConn))
                    {
                        CleanupRecord(context, e.TableAudit);
                    }
                }
                else
                {
                    logger.Info($"Upload AddBlackCard [Deleted], Card No: {e.TableAudit.BlacklistedCardDeleted} failed, should try again later");
                }
            }
        }

        #endregion

        #region 减量黑名单 (t_deleteBlackCard)

        //减量黑名单卡
        private void DbMonitor_OnReleasedCardDeleted(object sender, CardChangedEventArgs e)
        {
            if (e.TableAudit != null && !string.IsNullOrEmpty(e.TableAudit.ReleasedCardDeleted))
            {
                logger.Info($"Handling DeleteBlackCard record [Delete], Card No: {e.TableAudit.ReleasedCardDeleted}");

                var listedCard = new ListedCardInfo();
                listedCard.CardNo = e.TableAudit.ReleasedCardDeleted;
                listedCard.ListedType = ListedType.Deleted; // Delete black card
                listedCard.OperationType = 2; //delete

                var result = SendListedCardAsync(listedCard).Result;
                if (result.Success)
                {
                    logger.Info($"Upload DeleteBlackCard record [Deleted], Card No: {e.TableAudit.BlacklistedCardDeleted}, success");
                    using (var context = new SpsDbContext(_mySqlConn))
                    {
                        CleanupRecord(context, e.TableAudit);
                    }
                }
                else
                {
                    logger.Info($"Upload DeleteBlackCard record [Deleted], Card No: {e.TableAudit.BlacklistedCardDeleted} failed, should try again later");
                }
            }
        }

        private void DbMonitor_OnReleasedCardCreated(object sender, EntryFoundEventArgs e)
        {
            logger.Info("Handling DeleteBlackCard record [Create]");

            if (e.TableAudit != null && e.TableAudit.ReleasedCardCreated.HasValue)
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = context.TDeleteblackcard
                        .FirstOrDefault(r => r.Gid == Convert.ToUInt64(e.TableAudit.ReleasedCardCreated.Value));

                    if (record != null)
                    {
                        var listedCardInfo = new ListedCardInfo();
                        listedCardInfo.ListedType = ListedType.Deleted; //delete black card
                        listedCardInfo.Gid = Convert.ToInt64(record.Gid);
                        listedCardInfo.CardNo = record.CardNo;
                        listedCardInfo.ListedDate =
                            record.BlackDate == null ? DateTime.MinValue : record.BlackDate.Value;
                        listedCardInfo.AccountGid = Convert.ToInt64(record.AcctGid);
                        listedCardInfo.AccountId = record.AcctId;
                        listedCardInfo.CardType = Convert.ToByte(record.CardType);
                        listedCardInfo.DiscountNo =
                            record.DiscountNo == null ? 0 : record.DiscountNo.Value;
                        listedCardInfo.Reason = record.Reason;
                        listedCardInfo.UploadFlag = Convert.ToByte(record.UploadFlag);
                        listedCardInfo.OperationType = 0; // Create

                        var result = SendListedCardAsync(listedCardInfo).Result;

                        if (result.Success)
                        {
                            logger.Info($"Upload DeleteBlackCard record [Create]: {record.CardNo}, success");
                            CleanupRecord(context, e.TableAudit);
                        }
                        else
                        {
                            logger.Warn("Upload not successful, should try again later on DeleteBlackCard [Create]");
                        }
                    }
                    else
                    {
                        logger.Warn($"DeleteBlackCard record, gid: {e.TableAudit.ReleasedCardCreated.Value} not found!");
                        CleanupRecord(context, e.TableAudit);
                    }
                }
            }
        }

        #endregion

        #region 基础黑名单 (t_blackcard)

        // to be implemented...

        #endregion

        #region 灰名单 (t_grayinfo)

        private void DbMonitor_OnGrayInfoCreated(object sender, EntryFoundEventArgs e)
        {
            if (e.TableAudit.GrayInfoDeleted != null && e.TableAudit.GrayInfoCreated.Value != 0)
            {
                logger.Info($"Handling GrayInfo record [Create] Gid: {e.TableAudit.GrayInfoCreated.Value}");

                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var record = context.TGrayinfo
                        .FirstOrDefault(g => g.Gid == Convert.ToUInt64(e.TableAudit.GrayInfoCreated.Value));

                    if (record != null)
                    {
                        var grayInfo = CreateGrayInfoFromRecord(record);

                        var result = SendGrayInfoAsync(grayInfo).Result;

                        if (result.Success)
                        {
                            logger.Info($"Upload GrayInfo record [Create]: {record.CardNo}, success");
                            CleanupRecord(context, e.TableAudit);
                        }
                        else
                        {
                            logger.Warn("Upload not successful, should try again later on GrayInfo [Create]");
                        }
                    }
                    else
                    {
                        logger.Info($"Gray info, gid={e.TableAudit.GrayInfoCreated.Value} could not be found.");
                        CleanupRecord(context, e.TableAudit);
                    }
                }
            }
        }

        private GrayInfo CreateGrayInfoFromRecord(TGrayinfo record)
        {
            if (record == null)
                return null;

            var grayInfo = new GrayInfo();
            grayInfo.Gid = record.Gid;
            grayInfo.SNo = (int)record.Sno;
            grayInfo.PumpType = (byte)record.PumpType;
            grayInfo.CardNo = record.CardNo;
            grayInfo.PaymodeId = (byte)record.PaymodeId;
            grayInfo.TrdType = (byte)record.TrdType;
            grayInfo.CommId = record.CommId;
            grayInfo.Price = (uint)record.Prc;
            grayInfo.Volume = (uint)record.Vol;
            grayInfo.Money = (uint)record.Mon;
            grayInfo.RealMon = (uint)record.RealMon;
            grayInfo.CardBalance = (uint)record.CardBal;
            grayInfo.Ctc = (uint)record.Ctc;
            grayInfo.TtcTime =
                record.Ttctime.HasValue ? record.Ttctime.Value.ToString("yyyy-MM-dd HH:mm:ss") : "0001-01-01 00:00:00";
            grayInfo.TtcTimeEnd =
                record.TtctimeEnd.HasValue ? record.TtctimeEnd.Value.ToString("yyyy-MM-dd HH:mm:ss") : "0001-01-01 00:00:00";
            grayInfo.Ttc = (uint)record.Ttc;
            grayInfo.SeqNo = Convert.ToUInt16(record.SeqNo);
            grayInfo.NozNo = (byte)record.NozNo;
            grayInfo.PumpNo = (byte)record.PumpNo;
            grayInfo.PayTermId = (ulong)record.PayTemId;
            grayInfo.EndPumpId = (ulong)record.EndPumpId;
            grayInfo.DiscountNo = (uint)record.DiscountNo;
            grayInfo.PsamAsn = record.Psamasn;
            grayInfo.PsamTac = (uint)record.Psamtac;
            grayInfo.PsamTid = record.Psamtid;
            grayInfo.PsamTtc = (uint)record.Psamttc;
            grayInfo.Tac = (uint)record.Tac;
            grayInfo.Gmac = (uint)record.Gmac;
            grayInfo.Tmac = (uint)record.Tmac;
            grayInfo.UploadFlag = (byte)record.UploadFlag;
            grayInfo.OperationType = 0; // Record creation

            return grayInfo;
        }

        private void DbMonitor_OnGrayInfoDeleted(object sender, EntryFoundEventArgs e)
        {
            logger.Info($"Handling GrayInfo record [Delete], Card No: {e.TableAudit.GrayInfoDeleted}");

            var grayInfo = new GrayInfo();
            grayInfo.CardNo = e.TableAudit.GrayInfoDeleted;
            grayInfo.OperationType = 2; // Record Deletion
            var result = SendGrayInfoAsync(grayInfo).Result;

            if (result.Success)
            {
                logger.Info($"Upload GrayInfo record [Delete], Card No: {e.TableAudit.GrayInfoDeleted}, success");

                using (var context = new SpsDbContext(_mySqlConn))
                {
                    logger.Info($"Removing GrayInfo [Delete] audit entry");
                    CleanupRecord(context, e.TableAudit);
                }
            }
            else
            {
                logger.Warn("Upload not successful, should try again later on GrayInfo [Delete]");
            }
        }

        private async Task<SendResult> SendGrayInfoAsync(GrayInfo grayInfo)
        {
            var offlineRequest = new OfflineRequest();
            offlineRequest.GrayInfoList = new List<GrayInfo>();
            offlineRequest.GrayInfoList.Add(grayInfo);
            var result = await dataCourier.SendRequest(offlineRequest, HostOperationType.GrayInfo);
            return result;
        }

        #endregion

        #region Multiple records handling

        // Multiple `CREATED` acctinfo
        private void DbMonitor_OnMultipleAccountsCreated(object sender, MultipleAccountRecordsEventArgs e)
        {
            var auditCount = e.AuditRecords.Count();
            var accountInfoCount = e.AccountInfoRecords.Count();

            if (accountInfoCount > 0)
                logger.Info($"Multiple created account, count: {auditCount}, to be submitted count: {accountInfoCount}");

            try
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var result = SubmitMultipleAccountRecordsAsync(e.AccountInfoRecords, HostOperationType.CreateAccount).GetAwaiter().GetResult();

                    if (result.Success)
                    {
                        context.RemoveRange(e.AuditRecords);
                        var affectedRowCount = context.SaveChanges();

                        logger.Info($"Cleaning up multiple created account info, affected row count: {affectedRowCount}");
                    }
                }
            }
            catch (Exception ex)
            {
                logger.Error(ex.ToString());
            }
        }

        // Multiple `UPDATED` acctinfo
        private void DbMonitor_OnMultipleAccountsUpdated(object sender, MultipleAccountRecordsEventArgs e)
        {
            var auditCount = e.AuditRecords.Count();
            var accountInfoCount = e.AccountInfoRecords.Count();

            if (accountInfoCount > 0)
                logger.Info($"Multiple updated account, count: {auditCount}, to be submitted count: {accountInfoCount}");

            try
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var result = SubmitMultipleAccountRecordsAsync(e.AccountInfoRecords, HostOperationType.UpdateAccount).GetAwaiter().GetResult();

                    if (result.Success)
                    {
                        context.RemoveRange(e.AuditRecords);
                        var affectedRowCount = context.SaveChanges();

                        logger.Info($"Cleaning up multiple updated account info, affected row count: {affectedRowCount}");
                    }
                }
            }
            catch (Exception ex)
            {
                logger.Error(ex.ToString());
            }
        }

        // Multiple `CREATED` cardinfo
        private void DbMonitor_OnMultipleCardsCreated(object sender, MultipleCardRecordsEventArgs e)
        {
            var auditCount = e.AuditRecords.Count();
            var cardInfoCount = e.CardInfoRecords.Count();

            if (cardInfoCount > 0)
                logger.Info($"Multiple created cards, count: {auditCount}, to be submitted count: {cardInfoCount}");

            try
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var result = SubmitMultipleCardInfoAsync(e.CardInfoRecords, HostOperationType.CreateCard).GetAwaiter().GetResult();

                    if (result.Success)
                    {
                        context.RemoveRange(e.AuditRecords);
                        var affectedRowCount = context.SaveChanges();

                        logger.Info($"Cleaning up multiple created cards, affected row count: {affectedRowCount}");
                    }
                }
            }
            catch (Exception ex)
            {
                logger.Error(ex.ToString());
            }
        }

        // Multiple `UPDATED` cardinfo
        private void DbMonitor_OnMultipleCardsUpdated(object sender, MultipleCardRecordsEventArgs e)
        {
            var auditCount = e.AuditRecords.Count();
            var cardInfoCount = e.CardInfoRecords.Count();
            if (cardInfoCount > 0)
                logger.Info($"Multiple updated cards, count: {auditCount}, to be submitted count: {cardInfoCount}");

            try
            {
                using (var context = new SpsDbContext(_mySqlConn))
                {
                    var result = SubmitMultipleCardInfoAsync(e.CardInfoRecords, HostOperationType.UpdateCard).GetAwaiter().GetResult();

                    if (result.Success)
                    {
                        context.RemoveRange(e.AuditRecords);
                        var affectedRowCount = context.SaveChanges();

                        logger.Info($"Cleaning up multiple updated cards, affected row count: {affectedRowCount}");
                    }
                }
            }
            catch (Exception ex)
            {
                logger.Error(ex.ToString());
            }
        }

        #endregion

        #endregion

        #region Force upload transaction

        public async Task ForceTransactionUploadAsync(long gid)
        {
            logger.Info("Force uploading a transaction record...");

            if (dbMonitor.IsTransAlreadyUploaded(gid))
            {
                logger.Warn($"The transaction of GID: {gid} was uploaded, do nothing about it!");
                return;
            }

            var addResult = dbMonitor.AddToQueue(gid);

            if (!addResult)
            {
                logger.Info("No need to handle it now, since db monitor already found it");
                return;
            }

            logger.Info($"Ready to go ahead to upload transaction of GID: {gid}");
            using (var context = new SpsDbContext(_mySqlConn))
            {
                FuelProductInfo fuelProduct = new FuelProductInfo();

                var trans = await context.TTrdinfo.FirstOrDefaultAsync(t => t.Gid == Convert.ToUInt64(gid));

                if (trans != null)
                {
                    logger.Info($"   The transaction information of GID: {gid} is retrieved");

                    TTrdinfo grayTrade = null;
                    //In case it's ungray trade.
                    if (trans.TrdType == 2)
                    {
                        grayTrade = context.TTrdinfo
                            .FirstOrDefault(t => t.TrdType == 1 && t.CardNo == trans.CardNo
                                && t.Ctc == trans.Ctc && t.Ttctime == trans.Ttctime);
                    }

                    var fuel = context.TFuellist.FirstOrDefault(f => f.FuelNo == trans.CommId);

                    if (fuel != null)
                    {
                        logger.Info("fuel is not null");

                        fuelProduct.FuelName = fuel.Name;
                        fuelProduct.Barcode = GetBarcode(Convert.ToInt32(fuel.FuelNo)).ToString();
                    }
                    else
                    {
                        logger.Info("Fuel is null");
                        if (grayTrade != null)
                        {
                            logger.Info("get fuel from gray trans");

                            fuel = context.TFuellist.FirstOrDefault(f => f.FuelNo == grayTrade.CommId);
                            if (fuel != null)
                            {
                                fuelProduct.FuelName = fuel.Name;
                                fuelProduct.Barcode = GetBarcode(Convert.ToInt32(fuel.FuelNo)).ToString();
                                fuelProduct.Price = grayTrade.Prc.Value;
                            }
                        }
                    }

                    //var result = SubmitCardTrxRecordAsync(trans, fuelProduct).Result;
                    var result = await SubmitCardTrxRecordAsync(trans, grayTrade, fuelProduct);

                    logger.Info($"Force submit a new card payment record for " +
                        $"CardNo {trans.CardNo}, TTCTime: {trans.Ttctime}, GID: {trans.Gid}, Success? {result.Success}");

                    if (result.Success)
                    {
                        dbMonitor.AddToProcessedQueue(gid);

                        try
                        {
                            var currentTableAudit = await context.TTableaudit.FirstOrDefaultAsync(t => t.TransCreated == gid);
                            if (currentTableAudit != null)
                            {
                                var val = CleanupRecord(context, currentTableAudit);
                                logger.Info($"Cleaning up the Trx record from table, result: {val}");
                            }

                            dbMonitor.ClearFromQueue(gid);
                        }
                        catch (Exception ex)
                        {
                            logger.Error(ex);
                        }
                    }
                    else
                    {
                        dbMonitor.ClearFromQueue(gid);
                        logger.Warn($"Force upload result not success, should try again on New Card Transaction later, clear {gid} from queue");
                    }
                }
                else
                {
                    logger.Info($"This won't look good, the record of GID: {gid} could not be found! Clear it!");
                    dbMonitor.ClearFromQueue(gid);
                }
            }
        }

        #endregion

        private async Task<SendResult> SubmitAccountUpdate(TAcctinfo info)
        {
            var accountInfo = CreateAccountInfo(info);

            var result = await dataCourier.SendRequest(new OfflineRequest { AccountInfo = accountInfo }, HostOperationType.UpdateAccount);

            return result;
        }

        #region CreateAccountInfo - convert TAcctinfo to host account

        private AccountInfo CreateAccountInfo(TAcctinfo info)
        {
            AccountInfo accountInfo = new AccountInfo();

            accountInfo.Gid = Convert.ToInt64(info.Gid);
            accountInfo.SNo = Convert.ToInt16(info.Sno);
            accountInfo.AcctDate =
                info.AcctDate.HasValue ? info.AcctDate.Value.ToString("yyyy-MM-dd HH:mm:ss") /*.Replace(' ', 'T')*/ : "0001-01-01";
            accountInfo.AcctId = info.AcctId;
            accountInfo.AcctSNo = Convert.ToInt16(info.AcctSno);
            accountInfo.AcctState = Convert.ToByte(info.AcctState);
            accountInfo.AcctType = Convert.ToByte(info.AcctType);
            accountInfo.Address = info.Address;
            accountInfo.Amount = Convert.ToInt64(info.Amount);
            accountInfo.BelongTo = info.BelongTo;
            accountInfo.CertfType = info.CertfType;
            accountInfo.CertfNo = info.CertfNo;
            accountInfo.EnableSms = Convert.ToByte(info.EnableSms);
            accountInfo.FuelNo = info.FuelNo;
            accountInfo.Gift = Convert.ToInt32(info.Gift);
            accountInfo.PhoneNo = info.PhoneNo;
            accountInfo.RechgTotal = Convert.ToInt64(info.RechgTotal);
            accountInfo.TMac = Convert.ToInt64(info.Tmac);
            accountInfo.UploadFlag = Convert.ToByte(info.UploadFlag);

            accountInfo.WaitMalloc = info.Waitmalloc.Value > int.MaxValue ? 0 : Convert.ToUInt32(info.Waitmalloc);

            return accountInfo;
        }

        #endregion

        #region SUBMIT newly created Card Transaction

        private async Task<SendResult> SubmitCardTrxRecordAsync(TTrdinfo trx, TTrdinfo grayTrd, FuelProductInfo fuel = null)
        {
            OfflineTransactionInfo transInfo = new OfflineTransactionInfo();

            transInfo.Gid = Convert.ToInt64(trx.Gid);
            transInfo.SNo = Convert.ToInt16(trx.Sno);
            transInfo.ShiftNo = Convert.ToInt32(trx.ShiftNo);
            transInfo.PosId = Convert.ToByte(trx.Posid);
            transInfo.PumpType = Convert.ToByte(trx.PumpType);
            transInfo.Operator = trx.Operator;
            transInfo.CardNo = trx.CardNo;
            transInfo.OperCardNo = trx.OperCardNo;
            transInfo.PayModeId = trx.PaymodeId.HasValue ? (byte)trx.PaymodeId.Value : Convert.ToByte(0);
            transInfo.PayModeNo = trx.PaymodeNo;
            transInfo.TrdType = trx.TrdType;
            transInfo.CommId = trx.CommId;
            transInfo.Barcode = fuel == null ? "" : fuel.Barcode;
            transInfo.FuelName = fuel == null ? "" : fuel.FuelName;
            transInfo.Prc = trx.Prc == 0 ? Convert.ToInt32(fuel.Price) : Convert.ToInt32(trx.Prc);
            transInfo.Vol = Convert.ToInt32(trx.Vol);
            transInfo.Mon = Convert.ToInt32(trx.Mon);
            transInfo.RealMon = Convert.ToInt32(trx.RealMon);
            transInfo.CardBal = Convert.ToInt32(trx.CardBal);
            transInfo.CardType = Convert.ToByte(trx.CardType);
            transInfo.CTC = Convert.ToInt32(trx.Ctc);
            transInfo.TTCTime =
                trx.Ttctime.ToString("yyyy-MM-dd HH:mm:ss") /*.Replace(' ', 'T')*/;
            transInfo.TTCTimeEnd =
                trx.TtctimeEnd.HasValue ? trx.TtctimeEnd.Value.ToString("yyyy-MM-dd HH:mm:ss") /*.Replace(' ', 'T')*/ : "0001-01-01 00:00:00";

            if (transInfo.Mon == 0 && transInfo.RealMon == 0)
            {
                if (grayTrd != null)
                {
                    transInfo.CardBal += Convert.ToInt32(grayTrd.Mon - grayTrd.RealMon);
                    transInfo.TTCTimeEnd = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                }
            }

            transInfo.TTC = Convert.ToInt32(trx.Ttc);
            transInfo.SeqNo = Convert.ToInt32(trx.SeqNo);
            transInfo.BillNo = Convert.ToInt32(trx.BillNo);
            transInfo.NozNo = Convert.ToByte(trx.NozNo);
            transInfo.PumpNo = trx.PumpNo;
            transInfo.PayTermId = Convert.ToInt32(trx.PayTemId);
            transInfo.EndPump = Convert.ToInt64(trx.EndPumpId);
            transInfo.DiscountNo = Convert.ToInt32(trx.DiscountNo);
            transInfo.PsamAsn = trx.Psamasn;
            transInfo.PsamTac = Convert.ToInt64(trx.Psamtac);
            transInfo.PsamTid = trx.Psamtid;
            transInfo.PsamTTC = Convert.ToInt32(trx.Psamttc);
            transInfo.Tac = Convert.ToInt64(trx.Tac);
            transInfo.GMac = Convert.ToInt64(trx.Gmac);
            transInfo.Integral = Convert.ToInt32(trx.Integral);
            transInfo.UploadFlag = Convert.ToByte(trx.UploadFlag);
            transInfo.CarId = trx.CarId;
            transInfo.CarLicsNo = trx.CarLicsNo;
            transInfo.LineNo = trx.LineNo;
            transInfo.BillType = Convert.ToByte(trx.BillType);
            transInfo.BillFlag = Convert.ToByte(trx.BillFlag);

            var result = await
                dataCourier.SendRequest(new OfflineRequest { OfflineTrxInfo = transInfo }, HostOperationType.CreatePayRecord);

            return result;
        }

        private async Task<SendResult> SubmitCardTrxRecordAsync(TCardtrx trx)
        {
            OfflineTransactionInfo transInfo = new OfflineTransactionInfo();

            transInfo.Gid = Convert.ToInt64(trx.Gid);
            transInfo.SNo = Convert.ToInt16(trx.Sno);
            transInfo.ShiftNo = Convert.ToInt32(trx.ShiftNo);
            transInfo.PosId = Convert.ToByte(trx.Posid);
            transInfo.PumpType = Convert.ToByte(trx.PumpType);
            transInfo.Operator = trx.Operator;
            transInfo.CardNo = trx.CardNo;
            trx.PaymodeId = trx.PaymodeId;
            trx.PaymodeNo = trx.PaymodeNo;
            trx.TrdType = trx.TrdType;
            transInfo.CommId = trx.CommId;
            transInfo.Prc = Convert.ToInt32(trx.Prc);
            transInfo.Vol = Convert.ToInt32(trx.Vol);
            transInfo.Mon = Convert.ToInt32(trx.Mon);
            transInfo.RealMon = Convert.ToInt32(trx.RealMon);
            transInfo.CardBal = Convert.ToInt32(trx.CardBal);
            transInfo.CardType = Convert.ToByte(trx.CardType);
            transInfo.CTC = Convert.ToInt32(trx.Ctc);
            transInfo.TTCTime =
                trx.Ttctime.ToString("yyyy-MM-dd HH:mm:ss") /*.Replace(' ', 'T')*/;
            transInfo.TTCTimeEnd =
                trx.TtctimeEnd.HasValue ? trx.TtctimeEnd.Value.ToString("yyyy-MM-dd HH:mm:ss") /*.Replace(' ', 'T')*/ : "0001-01-01 00:00:00";
            transInfo.TTC = Convert.ToInt32(trx.Ttc);
            transInfo.SeqNo = Convert.ToInt32(trx.SeqNo);
            transInfo.BillNo = Convert.ToInt32(trx.BillNo);
            transInfo.NozNo = Convert.ToByte(trx.NozNo);
            transInfo.PumpNo = trx.PumpNo;
            transInfo.PayTermId = Convert.ToInt32(trx.PayTemId);
            transInfo.EndPump = Convert.ToInt64(trx.EndPumpId);
            transInfo.DiscountNo = Convert.ToInt32(trx.DiscountNo);
            transInfo.PsamAsn = trx.Psamasn;
            transInfo.PsamTac = Convert.ToInt64(trx.Psamtac);
            transInfo.PsamTid = trx.Psamtid;
            transInfo.PsamTTC = Convert.ToInt32(trx.Psamttc);
            transInfo.Tac = Convert.ToInt64(trx.Tac);
            transInfo.GMac = Convert.ToInt64(trx.Gmac);
            transInfo.Integral = Convert.ToInt32(trx.Integral);
            transInfo.UploadFlag = Convert.ToByte(trx.UploadFlag);
            transInfo.CarId = trx.CarId;
            transInfo.CarLicsNo = trx.CarLicsNo;
            transInfo.LineNo = trx.LineNo;
            transInfo.BillType = Convert.ToByte(trx.BillType);
            transInfo.BillFlag = Convert.ToByte(trx.BillFlag);

            var result = await
                dataCourier.SendRequest(new OfflineRequest { OfflineTrxInfo = transInfo }, HostOperationType.CreatePayRecord);

            return result;
        }

        #endregion

        #region Single and multiple accounts

        private async Task<SendResult> SubmitAccountRecordAsync(TAcctinfo info, HostOperationType operationType)
        {
            var accountInfo = CreateAccountInfo(info);

            var result = await dataCourier.SendRequest(new OfflineRequest { AccountInfo = accountInfo }, operationType);
            return result;
        }

        private async Task<SendResult> SubmitMultipleAccountRecordsAsync(IEnumerable<TAcctinfo> accountRecords, HostOperationType operationType)
        {
            List<AccountInfo> accountList = new List<AccountInfo>();
            foreach (var item in accountRecords)
            {
                var acct = CreateAccountInfo(item);
                accountList.Add(acct);
            }

            var result = await dataCourier.SendRequest(new OfflineRequest { AccountInfoList = accountList }, operationType);

            return result;
        }

        #endregion

        #region Single and multiple card info

        private async Task<SendResult> SubmitCardInfoAsync(TCardinfo info, HostOperationType operationType)
        {
            var cardInfo = ConvertDbCardInfoToHostCardInfo(info);

            var result = await dataCourier.SendRequest(new OfflineRequest { CardInfo = cardInfo }, operationType);

            return result;
        }

        private async Task<SendResult> SubmitMultipleCardInfoAsync(IEnumerable<TCardinfo> cardInfoRecords, HostOperationType operationType)
        {
            var cardInfoList = new List<CardInfo>();
            foreach (var item in cardInfoRecords)
            {
                var cardInfo = ConvertDbCardInfoToHostCardInfo(item);
                cardInfoList.Add(cardInfo);
            }

            var result = await dataCourier.SendRequest(new OfflineRequest { CardInfoList = cardInfoList }, operationType);
            return result;
        }

        #endregion

        #region Convert db cardinfo to host cardinfo

        private CardInfo ConvertDbCardInfoToHostCardInfo(TCardinfo info)
        {
            CardInfo cardInfo = new CardInfo();

            cardInfo.Gid = Convert.ToInt64(info.Gid);
            cardInfo.AcctId = info.AcctId;
            cardInfo.BLimitCar = Convert.ToByte(info.BLimitCar);
            cardInfo.BLmtGood = info.BLmtGood.HasValue ? Convert.ToBoolean(info.BLmtGood.Value) : false; //0=No restriction, 1=Restricted.
            cardInfo.CStatus = Convert.ToByte(info.CStatus);
            cardInfo.CardClass = Convert.ToByte(info.CardClass);
            cardInfo.CardId = Convert.ToInt64(info.CardId);
            cardInfo.CardNo = info.CardNo;
            cardInfo.CardSNo = Convert.ToInt16(info.CardSno);
            cardInfo.CardType = Convert.ToByte(info.CardType);
            cardInfo.CarNo = info.Carno;
            cardInfo.CTC = Convert.ToInt32(info.Ctc);
            cardInfo.CtcFlag = Convert.ToByte(info.Ctcflag);
            cardInfo.CtcTime = info.Ctctime;
            cardInfo.DiscountNo = Convert.ToInt32(info.DiscountNo);
            cardInfo.DMaxPay = Convert.ToInt32(info.DmaxPay);
            cardInfo.EnableSms = Convert.ToByte(info.EnableSms);
            cardInfo.Holder = info.Holder;
            cardInfo.IntegralTotal = Convert.ToInt32(info.IntegralTotal);
            cardInfo.KcDate =
                info.KcDate.HasValue ? info.KcDate.Value.ToString("yyyy-MM-dd HH:mm:ss") /*.Replace(' ', 'T')*/ : "0001-01-01 00:00:00";
            cardInfo.LimitTimes = Convert.ToByte(info.LimitTimes);
            cardInfo.LmtOil = info.LmtOil;
            cardInfo.MMaxPay = Convert.ToInt32(info.MmaxPay);
            cardInfo.Money = Convert.ToInt32(info.Money);
            cardInfo.OnceMaxPay = Convert.ToInt32(info.OnceMaxPay);
            cardInfo.OperNo = info.OperNo;
            cardInfo.OverDate =
                info.OverDate.HasValue ? info.OverDate.Value.ToString("yyyy-MM-dd HH:mm:ss") /*.Replace(' ', 'T')*/ : "9999-12-12 00:00:00";
            cardInfo.PhoneNo = info.PhoneNo;
            cardInfo.Pre_Malloc = Convert.ToInt32(info.PreMalloc);
            cardInfo.RechgTotal = Convert.ToUInt64(info.RechgTotal);
            cardInfo.SNo = Convert.ToInt16(info.Sno);
            cardInfo.StartDate =
                info.Startdate.HasValue ? info.Startdate.Value.ToString("yyyy-MM-dd HH:mm:ss") /*.Replace(' ', 'T')*/ : "0001-01-01 00:00:00";
            cardInfo.TMac = Convert.ToInt64(info.Tmac);
            cardInfo.UploadFlag = Convert.ToByte(info.UploadFlag);
            cardInfo.UserNo = info.UserNo;
            cardInfo.UserPin = info.UserPin;

            return cardInfo;
        }

        #endregion

        #region submit recharge/debit record

        private async Task<SendResult> SubmitRechargeReductionRecord(TRechdebitRep rep)
        {
            RechargeDebitInfo rdInfo = new RechargeDebitInfo();

            rdInfo.AcctId = rep.AcctId;
            rdInfo.Bal = Convert.ToInt32(rep.Bal);
            rdInfo.BillFlag = Convert.ToByte(rep.BillFlag);
            rdInfo.BillType = Convert.ToByte(rep.BillType);
            rdInfo.CardNo = rep.CardNo;
            rdInfo.CTC = Convert.ToInt32(rep.Ctc);
            rdInfo.DisIntegral = Convert.ToInt32(rep.DisIntegral);
            rdInfo.DisMoney = Convert.ToInt32(rep.DisMoney);
            rdInfo.DiscountNo = Convert.ToInt32(rep.DiscountNo);
            rdInfo.Gid = Convert.ToInt64(rep.Gid);
            rdInfo.Mon = Convert.ToInt32(rep.Mon);
            rdInfo.OperNo = rep.OperNo;
            rdInfo.RechgType = Convert.ToByte(rep.RechgType);
            rdInfo.SNo = Convert.ToInt16(rep.Sno);
            rdInfo.TMac = Convert.ToInt64(rep.Tmac);
            rdInfo.TrdType = Convert.ToByte(rep.TrdType);
            rdInfo.TTC = Convert.ToInt32(rep.Ttc);
            rdInfo.TTCTime =
                rep.Ttctime.HasValue ? rep.Ttctime.Value.ToString("yyyy-MM-dd HH:mm:ss") /*.Replace(' ', 'T')*/ : "0001-01-01 00:00:00";
            rdInfo.UploadFlag = Convert.ToByte(rep.UploadFlag);

            var result = await dataCourier.SendRequest(new OfflineRequest { RechargeDebitInfo = rdInfo }, HostOperationType.CreateRechargeRecord);

            return result;
        }

        #endregion

        #region Card Operation

        private async Task<SendResult> SubmitCardOperationInfoAsync(TCardreploss rl, string cardNo)
        {
            CardOperationInfo info = new CardOperationInfo();

            if (rl != null)
            {
                info.CardNo = rl.CardNo;
                info.AccountId = "";
                info.TimeStamp = rl.LossTime.Value.ToString("yyyy-MM-dd HH:mm:ss") /*.Replace(' ', 'T')*/;
                info.OperationType = rl.OperType.Value;
            }
            else if (!string.IsNullOrEmpty(cardNo))
            {
                info.CardNo = cardNo;
                info.AccountId = "";
                info.TimeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") /*.Replace(' ', 'T')*/;
                info.OperationType = 3;
            }

            var result = await dataCourier.SendRequest(new OfflineRequest { CardOperationInfo = info }, HostOperationType.CardOperation);

            return result;
        }

        #endregion

        private async Task<SendResult> SubmitCardRepLossAsync(TCardreploss rl)
        {
            ReportedLostCard cardRepLoss = new ReportedLostCard();
            cardRepLoss.Gid = Convert.ToInt64(rl.Gid);
            cardRepLoss.CardNo = rl.CardNo;
            cardRepLoss.SNo = Convert.ToUInt16(rl.Sno);
            cardRepLoss.LossTime = rl.LossTime.Value.ToString("yyyy-MM-dd HH:mm:ss");
            cardRepLoss.OperType = rl.OperType.Value;
            cardRepLoss.OperNo = rl.OperNo;
            cardRepLoss.Reason = rl.Reason;
            cardRepLoss.OperationType = 0; // 0=created.

            OfflineRequest request = new OfflineRequest();
            request.CardRepLossInfoList = new List<ReportedLostCard>();
            request.CardRepLossInfoList.Add(cardRepLoss);

            var result = await dataCourier.SendRequest(request, HostOperationType.CardRepLoss);

            return result;
        }

        #region Blacklisted card / Unblocked card

        private async Task<SendResult> SendListedCardAsync(ListedCardInfo listedCardInfo)
        {
            var offlineRequest = new OfflineRequest();
            offlineRequest.BlackCardInfoList = new List<BlackCardInfo>();

            var blackCard = new BlackCardInfo();
            blackCard.Gid = listedCardInfo.Gid;
            blackCard.CardNo = listedCardInfo.CardNo;
            blackCard.AcctGid = listedCardInfo.AccountGid;
            blackCard.AcctId = listedCardInfo.AccountId;
            blackCard.CardType = listedCardInfo.CardType;
            blackCard.DiscountNo = listedCardInfo.DiscountNo;
            blackCard.Reason = listedCardInfo.Reason;
            blackCard.UploadFlag = listedCardInfo.UploadFlag;
            blackCard.BlackDate = listedCardInfo.ListedDate.ToString("yyyy-MM-dd HH:mm:ss");
            blackCard.OperationType = listedCardInfo.OperationType; // record added, removed or updated

            if (listedCardInfo.ListedType == ListedType.Base)
            {
                blackCard.BlackType = 0; //base black card
                offlineRequest.BlackCardInfoList.Add(blackCard);
            }
            else if (listedCardInfo.ListedType == ListedType.Add)
            {
                blackCard.BlackType = 1; //AddBlackCard
                offlineRequest.BlackCardInfoList.Add(blackCard);
            }
            else if (listedCardInfo.ListedType == ListedType.Deleted)
            {
                blackCard.BlackType = 2; //DeleteBlackCard
                offlineRequest.BlackCardInfoList.Add(blackCard);
            }

            var result = await dataCourier.SendRequest(offlineRequest, HostOperationType.ListedCard);

            return result;
        }

        #endregion

        private void StartDbMonitor()
        {
            if (mre != null)
                mre.WaitOne();
            else
                logger.Error("DbMonitor mre is null");

            if (dbMonitor != null)
                dbMonitor.Start();
            else
                logger.Error("DbMonitor instance is null");
        }

        private bool CheckTable()
        {
            using (var context = new SpsDbContext(_mySqlConn))
            {
                var conn = context.Database.GetDbConnection();

                if (conn.State.Equals(ConnectionState.Closed))
                {
                    conn.Open();
                }

                using (var command = conn.CreateCommand())
                {
                    command.CommandText = @"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 't_tableaudit'";

                    var exists = Convert.ToInt32(command.ExecuteScalar()) != 0;

                    logger.Info($"The table t_tableaudit exists? {exists}");

                    return exists;
                }
            }
        }

        private bool UploadExistingData()
        {
            if (dataCourier != null)
                dataCourier.GetToken();
            else
                logger.Error("DataCourier instance is null");

            if (!CheckTable())
            {
                //mre.Set();
                logger.Error("*** The table t_tableaudit does not exist! Consider initialize the table first!");
                return true;
            }

            //Existing data already being uploaded.
            if (System.IO.File.Exists("sps.txt"))
            {
                mre.Set();
                logger.Info("Existing data already uploaded!");
                return true;
            }

            var uploadAccount = UploadAccounts();
            var uploadCardInfo = UploadCardInfo();
            var uploadClosedCard = UploadClosedCards();
            var uploadRechRedt = UploadRechargeReductionRecords();
            var uploadCardTrx = UploadCardTransactions();

            if (uploadAccount && uploadCardInfo && uploadCardTrx && uploadRechRedt)
            {
                logger.Info("All existing records are uploaded successfully, WOW!");
            }

            if (uploadClosedCard)
                logger.Info("Uploaded all from t_cardlogout");

            System.IO.File.Create("sps.txt");
            mre.Set();

            return true;
        }

        #region Process existing records

        private IEnumerable<TAcctinfo> GetExistingAccounts()
        {
            using (var guardDbContext = new GuardDbContext())
            using (var context = new SpsDbContext(_mySqlConn))
            {
                var account = context.TTableaudit.FirstOrDefault(t => t.AccountCreated.HasValue && t.AccountCreated.Value != 0);

                if (account != null)
                {
                    var existingAccounts = context.TAcctinfo.Where(t => t.Gid < Convert.ToUInt64(account.AccountCreated.Value)).AsEnumerable();

                    if (guardDbContext.AccountUpload.Any())
                    {
                        var maxGid = guardDbContext.AccountUpload.Max(a => a.Gid);
                        logger.Info($"Already uploaded some Account info, max existing Gid: {maxGid}");
                        return existingAccounts.Where(a => Convert.ToInt64(a.Gid) > maxGid).ToList();
                    }

                    logger.Info($"Existing account count: {existingAccounts.Count()}");

                    return existingAccounts.ToList();
                }
                else
                {
                    logger.Info($"No new account created, get all the accounts from `t_acctinfo`");

                    return context.TAcctinfo.ToList();
                }
            }
        }

        private bool UploadAccounts()
        {
            var accounts = GetExistingAccounts();

            if (accounts == null)
            {
                logger.Error("** No existing account identified, skip uploading");
                return false;
            }

            logger.Info($"Existing account count: {accounts.Count()}");

            using (var context = new GuardDbContext())
            {
                foreach (var account in accounts)
                {
                    var result = SubmitAccountRecordAsync(account, HostOperationType.CreateAccount).Result;

                    context.Add(new AccountUpload
                    {
                        Gid = Convert.ToInt64(account.Gid),
                        AccountId = account.AcctId,
                        Operation = 1,
                        OperationTime = DateTime.Now,
                        Status = result.Success ? 0 : 1
                    });

                    context.SaveChanges();
                }

                var failedUploads = context.AccountUpload.Where(a => a.Status > 0);

                if (failedUploads != null && failedUploads.Count() > 0)
                {
                    logger.Info("Existing accounts uploaded with failures\n");
                    return false;
                }
                else
                {
                    logger.Info("Existing accounts uploaded successfully\n");
                    return true;
                }
            }
        }

        private IEnumerable<TCardinfo> GetExistingCards()
        {
            using (var guardDbContext = new GuardDbContext())
            using (var context = new SpsDbContext(_mySqlConn))
            {
                var cardInfo = context.TTableaudit.FirstOrDefault(t => t.CardInfoCreated.HasValue && t.CardInfoCreated.Value != 0);

                if (cardInfo != null)
                {
                    var cards = context.TCardinfo.Where(t => t.Gid < Convert.ToUInt64(cardInfo.CardInfoCreated.Value)).AsEnumerable();

                    if (guardDbContext.CardUpload.Any())
                    {
                        var maxGid = guardDbContext.CardUpload.Max(c => c.Gid);
                        logger.Info($"Already uploaded some Card info, max existing Gid: {maxGid}");
                        return cards.Where(c => Convert.ToInt64(c.Gid) > maxGid);
                    }

                    logger.Info($"Existing card info count: {cards.Count()}");
                    return cards.ToList();
                }
                else
                {
                    logger.Info("No new card info, get all card info from table `t_cardinfo`");

                    return context.TCardinfo.ToList();
                }
            }
        }

        private bool UploadCardInfo()
        {
            var cards = GetExistingCards();

            logger.Info($"Existing card count: {cards.Count()}");

            using (var context = new GuardDbContext())
            {
                foreach (var card in cards)
                {
                    var result = SubmitCardInfoAsync(card, HostOperationType.CreateCard).Result;

                    context.Add(new CardUpload
                    {
                        Gid = Convert.ToInt64(card.Gid),
                        CardNo = card.CardNo,
                        Operation = 1, //Insert
                        OperationTime = DateTime.Now,
                        Status = result.Success ? 0 : 1 //0 = OK, 1 = NOK
                    });

                    context.SaveChanges();
                }

                var failedUploads = context.CardUpload.Where(c => c.Status > 0);

                if (failedUploads != null && failedUploads.Count() > 0)
                {
                    logger.Info("Existing card uploaded with failures\n");
                    return false;
                }
                else
                {
                    logger.Info("Existing card uploaded successfully\n");
                    return true;
                }
            }
        }

        private IEnumerable<TCardlogout> GetExistingClosedCards()
        {
            using (var context = new SpsDbContext(_mySqlConn))
            {
                logger.Info("Get all card info from table `t_cardlogout`");
                return context.TCardlogout.ToList();
            }
        }

        private bool UploadClosedCards()
        {
            var cards = GetExistingClosedCards();

            logger.Info($"Existing closed card count: {cards.Count()}");

            using (var context = new GuardDbContext())
            {
                foreach (var c in cards)
                {
                    var card = ConvertClosedCardToCardInfo(c);
                    var result = SubmitCardInfoAsync(card, HostOperationType.CreateCard).Result;

                    context.Add(new CardUpload
                    {
                        Gid = Convert.ToInt64(card.Gid),
                        CardNo = card.CardNo,
                        Operation = 1, //Insert
                        OperationTime = DateTime.Now,
                        Status = result.Success ? 0 : 1 //0 = OK, 1 = NOK
                    });

                    context.SaveChanges();
                }

                var failedUploads = context.CardUpload.Where(c => c.Status > 0);

                if (failedUploads != null && failedUploads.Count() > 0)
                {
                    logger.Info("Existing cardlogout uploaded with failures\n");
                    return false;
                }
                else
                {
                    logger.Info("Existing cardlogout uploaded successfully\n");
                    return true;
                }
            }
        }

        private TCardinfo ConvertClosedCardToCardInfo(TCardlogout closedCard)
        {
            var cardInfo = new TCardinfo();
            cardInfo.AcctGid = closedCard.AcctGid;
            cardInfo.AcctId = closedCard.AcctId;
            cardInfo.CardNo = closedCard.CardNo;
            cardInfo.CardId = closedCard.CardId.HasValue ? closedCard.CardId.Value : 0;
            cardInfo.CardType = closedCard.CardType.HasValue? Convert.ToByte(closedCard.CardType.Value): (byte)1;
            cardInfo.Holder = closedCard.Holder;
            cardInfo.PhoneNo = closedCard.PhoneNo;
            cardInfo.KcDate = closedCard.KcDate;
            cardInfo.Startdate = closedCard.Startdate;
            cardInfo.CStatus = 2; // 2 = 注销卡
            return cardInfo;
        }

        private IEnumerable<TTrdinfo> GetExistingCardTrans()
        {
            using (var context = new SpsDbContext(_mySqlConn))
            {
                var trx = context.TTableaudit.FirstOrDefault(a => a.TransCreated.HasValue && a.TransCreated.Value != 0);

                if (trx != null)
                {
                    var transactions = context.TTrdinfo.Where(t => t.Gid < trx.Gid && t.Mon > 0);

                    logger.Info($"Existing card transactions count: {transactions.Count()}");

                    return transactions.ToList();
                }
                else
                {
                    return context.TTrdinfo.ToList();
                }
            }
        }

        private bool UploadCardTransactions()
        {
            logger.Info("Start to get existing card transactions");
            var transactions = GetExistingCardTrans();
            logger.Info("Pulled all the existing card transactions from database");

            using (var spsContext = new SpsDbContext(_mySqlConn))
            using (var context = new GuardDbContext())
            {
                foreach (var trans in transactions)
                {
                    FuelProductInfo fuelProduct = new FuelProductInfo();
                    var fuel = spsContext.TFuellist.FirstOrDefault(f => f.FuelNo == trans.CommId);

                    if (fuel != null)
                    {
                        fuelProduct.FuelName = fuel.Name;
                        fuelProduct.Barcode = GetBarcode(Convert.ToInt32(fuel.FuelNo)).ToString();
                    }

                    var result = SubmitCardTrxRecordAsync(trans, null, fuelProduct).Result;

                    context.TradeUpload.Add(new TradeUpload
                    {
                        Gid = Convert.ToInt64(trans.Gid),
                        Operation = 1,
                        OperationTime = DateTime.Now,
                        Status = result.Success ? 0 : 1
                    });

                    context.SaveChanges();
                }

                var failedUploads = context.TradeUpload.Where(c => c.Status > 0);

                if (failedUploads != null && failedUploads.Count() > 0)
                {
                    logger.Info("Existing card trx uploaded with failures\n");
                    return false;
                }
                else
                {
                    logger.Info("Existing card trx uploaded successfully\n");
                    return true;
                }
            }
        }

        private IEnumerable<TRechdebitRep> GetExistingRechargeReductions()
        {
            using (var context = new SpsDbContext(_mySqlConn))
            {
                var record = context.TTableaudit.FirstOrDefault(t => t.RechargeCreated.HasValue && t.RechargeCreated.Value != 0);

                if (record != null)
                {
                    var records = context.TRechdebitRep.Where(r => r.Gid <= Convert.ToUInt64(record.RechargeCreated.Value)).AsEnumerable();

                    logger.Info($"Existing recharge reduction operations count: {records.Count()}");

                    return records.ToList();
                }
                else
                {
                    logger.Info("No new recharge reduction records");
                    return context.TRechdebitRep.ToList();
                }
            }
        }

        private bool UploadRechargeReductionRecords()
        {
            var records = GetExistingRechargeReductions();

            using (var context = new GuardDbContext())
            {
                foreach (var r in records)
                {
                    var result = SubmitRechargeReductionRecord(r).Result;

                    context.RechargeUpload.Add(new RechargeUpload
                    {
                        Gid = Convert.ToInt64(r.Gid),
                        AccountId = r.AcctId,
                        CardNo = r.CardNo,
                        Operation = 1,
                        OperationTime = DateTime.Now,
                        Status = result.Success ? 0 : 1
                    });

                    context.SaveChanges();
                }

                var failedUploads = context.RechargeUpload.Where(r => r.Status > 0);

                if (failedUploads != null && failedUploads.Count() > 0)
                {
                    logger.Info("Existing recharge/reductions uploaded with failures\n");
                    return false;
                }
                else
                {
                    logger.Info("Existing recharge/reductions uploaded successfully\n");
                    return true;
                }
            }
        }

        #endregion

        private void HandleMissingCard(SpsDbContext spsDbContext, string cardNo)
        {
            if (!string.IsNullOrEmpty(cardNo))
            {
                var missingCard = spsDbContext.TCardinfo.FirstOrDefault(c => c.CardNo == cardNo);
                if (missingCard != null)
                {
                    var result = SubmitCardInfoAsync(missingCard, HostOperationType.CreateCard).Result;

                    logger.Info($"Submit required CardInfo for CardNo: {missingCard.CardNo}, GID: {missingCard.Gid}, Success? {result.Success}");

                    if (result.Code == 4)
                    {
                        HandleMissingAccount(spsDbContext, missingCard.AcctId);
                    }
                }
            }
        }

        private void HandleMissingAccount(SpsDbContext spsDbContext, string accountId)
        {
            if (!string.IsNullOrEmpty(accountId))
            {
                var missingAccount = spsDbContext.TAcctinfo.FirstOrDefault(a => a.AcctId == accountId);
                if (missingAccount != null)
                {
                    var result = SubmitAccountRecordAsync(missingAccount, HostOperationType.CreateAccount).Result;

                    logger.Info($"Submit required AccountInfo for AccoutId: {missingAccount.AcctId}, " +
                        $"GID: {missingAccount.Gid}, Success? {result.Success}");
                }
            }
        }
    }


    #region Config parameters

    public class DataCourierAppContructorParameterV1
    {
        public int Id { get; set; }

        public string Username { get; set; }

        public string Password { get; set; }

        public string AuthServiceBaseUrl { get; set; }

        public string AccountServiceBaseUrl { get; set; }

        public string AccountServiceRelativeUrl { get; set; }

        public string DeviceSN { get; set; }

        public int ScanInterval { get; set; }

        public List<FuelMappingV1> FuelMappingArr { get; set; }

        public int RetryCount { get; set; }

        public bool EnableSync { get; set; }

        public bool ExcludeCurrentSite { get; set; }

        public int SyncInterval { get; set; }

        public string CheckVersionRelativeUrl { get; set; }

        public string SyncDataRelativeUrl { get; set; }

        public SpsDbConnectionSetting ConnectionString { 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
}