using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace Dfs.WayneChina.CardTrxManager
{
    public class StoreForwardCompletedEventArgs : EventArgs
    {
        public int PumpdId { get; set; }

        public int SeqNo { get; set; }

        public int ReleaseToken { get; set; }
    }

    public class StoreForwardManager
    {
        static NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("PosTrxSubmitter");

        private TrxSubmitter.TrxSubmitter submitter;

        private TransactionManager transactionManager;

        public event EventHandler<StoreForwardCompletedEventArgs> OnStoreForwardCompleted;

        private System.Timers.Timer scanTimer;
        private int count = 0;

        public StoreForwardManager(CloudCredential credential)
        {
            submitter = new TrxSubmitter.TrxSubmitter(100, credential);
            transactionManager = new TransactionManager();

            scanTimer = new System.Timers.Timer();
            scanTimer.Elapsed += ScanTimer_Elapsed;
            scanTimer.Interval = 30000;
        }

        private async void ScanTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            scanTimer.Stop();

            count++;
            var firstFileName = Directory.GetFiles("StoreForward").FirstOrDefault();

            if (!string.IsNullOrEmpty(firstFileName))
            {
                logger.Info($"  SF, {count}, found file: {firstFileName}");
                var sfTrans = transactionManager.TryLoad(firstFileName);
                if (sfTrans != null)
                {
                    logger.Info($"  SF, {count}, Transaction reloaded");
                    var result = await submitter.SubmitTrxAsync(sfTrans);

                    logger.Info($"  SF, {count}, submit result: {result}");

                    if (result)
                    {
                        FuelingDoneItem fuelingDoneItem = (FuelingDoneItem)sfTrans.TransactionItems.FirstOrDefault(t => t is FuelingDoneItem);

                        if (fuelingDoneItem != null)
                        {
                            OnStoreForwardCompleted?.Invoke(this, new StoreForwardCompletedEventArgs
                            {
                                PumpdId = fuelingDoneItem.PumpId,
                                ReleaseToken = fuelingDoneItem.FdcSqNo,
                                SeqNo = fuelingDoneItem.SeqNo
                            });
                        }

                        try
                        {
                            File.Delete(firstFileName);
                            logger.Info($"  SF success, delete file: {firstFileName}");
                        }
                        catch (Exception ex)
                        {
                            logger.Error("Exception in deleting file " + ex.ToString());
                        }
                    }
                }
            }

            scanTimer.Start();
        }

        public void Start()
        {
            transactionManager.Init();
            scanTimer.Start();
        }
    }


    public class TransactionManager
    {
        private readonly string storeForwardFolder = "StoreForward";
        private readonly string errorFolder = @"StoreForward/Error";

        private XmlSerializer xmlSerializer;

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

        public TransactionManager()
        {
            xmlSerializer = new XmlSerializer(typeof(StoreFowardTransaction), transactionItemTypes);
        }

        public void Init()
        {
            if (!Directory.Exists(storeForwardFolder))
                Directory.CreateDirectory(storeForwardFolder);

            if (!Directory.Exists(errorFolder))
                Directory.CreateDirectory(errorFolder);
        }

        Type[] transactionItemTypes =
        {
            typeof(TransactionItem),
            typeof(TransactionCreatedItem),
            typeof(ProductCodeIdentitiedItem),
            typeof(FuelingDoneItem),
            typeof(CommittedItem)
        };


        public void SaveTransaction(StoreFowardTransaction transaction)
        {
            Save(storeForwardFolder, transaction);
        }

        public StoreFowardTransaction TryLoad(string fileName)
        {
            try
            {
                using (var reader = new StreamReader(fileName))
                {
                    var transaction = (StoreFowardTransaction)xmlSerializer.Deserialize(reader);
                    return transaction;
                }
            }
            catch (Exception ex)
            {
                logger.Error($"TM: {ex.ToString()}");
            }

            return null;
        }

        public void SetTransactionToError(StoreFowardTransaction transaction)
        {
            Save(errorFolder, transaction);
        }

        private void Save(string targetLocation, StoreFowardTransaction transaction)
        {
            FuelingDoneItem fuelingDoneItem = 
                transaction.TransactionItems.FirstOrDefault(i => i is FuelingDoneItem) as FuelingDoneItem;

            if (fuelingDoneItem != null)
            {
                string fileName = $"Trans_{fuelingDoneItem.PumpId}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.xml";

                try
                {
                    using (var sw = new StreamWriter(targetLocation + "/" + fileName))
                    {
                        xmlSerializer.Serialize(sw, transaction);
                    }
                }
                catch (Exception ex)
                {
                    logger.Error($"TM: {ex.ToString()}");
                }
            }
            else
            {
                logger.Error("TM: No FuelingDoneItem");
            }
        }
    }


    [XmlRoot("SFTrans")]
    [XmlInclude(typeof(TransactionItem))]
    public class StoreFowardTransaction
    {
        public StoreFowardTransaction()
        {
            TransactionItems = new List<TransactionItem>();
        }

        [XmlArray("Items")]
        [XmlArrayItem("TransactionItem")]
        public List<TransactionItem> TransactionItems { get; }

        public int ErrorItemsCount
        {
            get
            {
                int count = 0;
                for (int i = 0; i < TransactionItems.Count; i++)
                {
                    if (TransactionItems[i] is ErrorItem)
                        count++;
                }

                return count;
            }
        }

        public TransactionState LastState => TransactionItems.OrderBy(t => t.AddedTime).Last().State;
        
        public int FuelingId
        {
            get
            {
                var fuelingDoneItem = TransactionItems.FirstOrDefault(t => t is FuelingDoneItem) as FuelingDoneItem;
                if (fuelingDoneItem != null)
                    return fuelingDoneItem.SeqNo;

                return 0;
            }
        }
    }

    [XmlType("TransactionItem")] // define Type
    [XmlInclude(typeof(FuelingDoneItem)),
     XmlInclude(typeof(ProductCodeIdentitiedItem)),
     XmlInclude(typeof(TransactionCreatedItem)),
     XmlInclude(typeof(CommittedItem))]
    public class TransactionItem
    {
        public TransactionItem()
        {
            AddedTime = DateTime.Now;
        }

        [XmlAttribute("State")]
        public TransactionState State { get; set; }

        public DateTime AddedTime { get; set; }
    }

    public enum TransactionState
    {
        Unknown,
        FuelingDone,
        ProductCodeIdentified,
        Created,
        Committed,
        Error
    }

    [XmlType("FuelingDoneItem")]
    public class FuelingDoneItem : TransactionItem
    {
        public FuelingDoneItem()
        {
            State = TransactionState.FuelingDone;
        }

        public int Barcode { get; set; }
        public int PumpId { get; set; }
        public int NozzleId { get; set; }
        public int SiteNozzleNo { get; set; }
        public ushort FPosSqNo { get; set; }
        public int FdcSqNo { get; set; }
        public DateTime TimeStamp { get; set; }
        public decimal Volume { get; set; }
        public decimal Amount { get; set; }
        public decimal PayAmount { get; set; }
        public decimal UnitPrice { get; set; }
        public int SeqNo { get; set; }
        public string CardNo { get; set; }
        public decimal CurrentCardBalance { get; set; }
        public DateTime FuelingStartTime { get; set; }
        public DateTime FuelingFinishedTime { get; set; }
        public decimal VolumeTotalizer { get; set; }
        public string CardHolder { get; set; }
        public string AccountName { get; set; }
    }

    [XmlType("ProductCodeIdentitiedItem")]
    public class ProductCodeIdentitiedItem : TransactionItem
    {
        public ProductCodeIdentitiedItem()
        {
            State = TransactionState.ProductCodeIdentified;
        }

        public Guid ItemId { get; set; }
    }

    [XmlType("TransactionCreatedItem")]
    public class TransactionCreatedItem : TransactionItem
    {
        public TransactionCreatedItem()
        {
            State = TransactionState.Created;
        }

        public Guid TransactionId { get; set; }
    }

    [XmlType("CommittedItem")]
    public class CommittedItem : TransactionItem
    {
        public CommittedItem()
        {
            State = TransactionState.Committed;
        }

        public bool Success { get; set; }
    }

    [XmlType("ErrorItem")]
    public class ErrorItem : TransactionItem
    {
        public ErrorItem()
        {
            State = TransactionState.Error;
        }
    }
}