using Applications.FDC;
using Edge.Core.Database;
using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump;
using FdcServerHost;
using FspWebApp.Entity.Client;
using FspWebApp.Entity.Service;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Wayne.FDCPOSLibrary;
using Edge.Core.Configuration;

namespace FspWebApp
{
    public class FspWebRun : IAppProcessor
    {
        #region Properties

        public string MetaConfigName { get; set; }

        

        public static string LocalServiceUrl { get; set; }

        public string AuthServiceBaseUrl { get; set; }

        public string TransactionServiceBaseUrl { get; set; }

        #endregion

        #region Fields

        private ILogger logger = NullLogger.Instance;

        private object lockTransactionMonitor = new object();

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

        private FdcServerHostApp fdcServerHostApp;

        #endregion

        #region Constructor

        public FspWebRun(string otherParameter, string localServiceUrl, string authServiceBaseUrl, string transactionServiceBaseUrl, IServiceProvider services)
        {
            if (services != null)
            {
                var loggerFactory = services.GetRequiredService<ILoggerFactory>();
                this.logger = loggerFactory.CreateLogger("FspWebApp");
            }

            string[] args = { };
            LocalServiceUrl = localServiceUrl;
            AuthServiceBaseUrl = authServiceBaseUrl;
            TransactionServiceBaseUrl = transactionServiceBaseUrl;
            ThreadPool.QueueUserWorkItem(RunWeb, args);

            //var ts = new Test(36);
        }

        #endregion

        #region Init

        private void RunWeb(object state)
        {
            CreateWebHostBuilder(state as string[]).Build().Run();
        }

        private static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args).UseUrls(LocalServiceUrl).UseStartup<Startup>()
            .ConfigureLogging(logging =>
            {
                logging.ClearProviders();
            });

        //IApplication method implementation
        public void Init(IEnumerable<IProcessor> processors)
        {
            InfoLog("FspWebApp Init begins...");
            try
            {
                fdcServerHostApp = processors.OfType<FdcServerHostApp>().FirstOrDefault();
                if (fdcServerHostApp == null)
                    throw new ArgumentNullException("Can't find the FdcServerHostApp from processors");

                FuelProduct.onChangeFuelPrice = new ChangeFuel(FdcServerHostApp_OnChangeFuelPrice);
                FuelProduct.onChangeFuelProduct = new ChangeFuel(FdcServerHostApp_OnChangeFuelProduct);
                FuelProduct.onGetAuthToken = new CommonDelegate(FdcServerHostApp_OnGetAuthToken);

                var sino = Configurator.Default.DeviceProcessorConfiguration.Processor.Find(i => i.Parameter.Find(p => p.Name == "rawProductNameToPosProductNameStr") != null);
                string[] productNames = sino.Parameter.Find(i => i.Name == "rawProductNameToPosProductNameStr").Value.Split(';', StringSplitOptions.RemoveEmptyEntries);
                foreach (string pn in productNames)
                {
                    string[] bcpn = pn.Split(':', StringSplitOptions.RemoveEmptyEntries);
                    int barcode = int.Parse(bcpn[0]);
                    DevicesConfig.fuelProducts[barcode] = new FuelProduct()
                    {
                        Barcode = barcode,
                        FuelName = bcpn[1],
                        CurrentPrice = 0.0,
                        CurrentNozzles = new HashSet<int>()
                    };
                }

                nozzles.Clear();
                InfoLog("(PumpId, Nozzle.LogicalId) => Site nozzle number");

                IEnumerable<NozzleExtraInfo> nozzleProductConfig = Configurator.Default.NozzleExtraInfoConfiguration.Mapping;
                foreach (NozzleExtraInfo np in nozzleProductConfig)
                {
                    InfoLog($"({np.PumpId}, {np.NozzleLogicalId}) => {np.SiteLevelNozzleId}");
                    nozzles.Add((np.PumpId, np.NozzleLogicalId), np.SiteLevelNozzleId ?? 0);
                    DevicesConfig.fuelProducts[np.ProductBarcode].CurrentNozzles.Add(np.SiteLevelNozzleId ?? 0);
                }

                InitPumpData(fdcServerHostApp.FdcPumpControllers);

                DevicesConfig.pumpDatas = DevicesConfig.pumpDatas.OrderBy(o => o.Key).ToDictionary(o => o.Key, p => p.Value);
                DevicesConfig.fuelProducts = DevicesConfig.fuelProducts.OrderBy(o => o.Key).ToDictionary(o => o.Key, p => p.Value);
            }
            catch (Exception ex)
            {
                logger.LogError($"Init Exception: {ex}");
            }

            InfoLog("FspWebApp init ends...");
        }

        #endregion

        #region IProcessor implementation

        public async Task<bool> Start()
        {
            InfoLog("FspWebApp Start begins...");

            fdcServerHostApp.OnStateChange += FdcPumpController_OnStateChange;
            fdcServerHostApp.OnCurrentFuellingStatusChange += FdcPumpController_OnCurrentFuellingStatusChange;
            fdcServerHostApp.OnFdcFuelSaleTransactinStateChange += FdcPumpController_OnFdcFuelSaleTransactinStateChange;

            InfoLog("FspWebApp Start ends...");

            return true;
        }

        public async Task<bool> Stop()
        {

            return true;
        }

        private async Task<int> TransactionMonitor(int pumpId, byte logicalId)
        {
            try
            {
                var fuelPointTotals = await fdcServerHostApp.GetFuelPointTotalsAsync(pumpId, logicalId);
                lock (lockTransactionMonitor)
                {
                    DevicesConfig.pumpDatas[nozzles[(pumpId, logicalId)]].VolumeTotalizer = fuelPointTotals.Item2;
                }
            }
            catch (Exception ex)
            {
                logger.LogError($"TransactionMonitor Exception: {ex}");
            }

            return 0;
        }

        #endregion

        #region Init pump data

        /// <summary>
        /// Set up the site nozzles, in particular the nozzle number
        /// Strategy:
        ///     1. Sort the Pumps by id
        ///     2. Sort the nozzles by logical id
        ///     3. The site nozzle number then will be (Pump id, Logical nozzle id) => site nozzle number
        ///        e.g. (1, 1) => 1, (1, 2) => 2, (2, 1) => 3, (3, 1) => 4, (3, 2) => 5, (4, 1) => 6 ...
        /// </summary>
        /// <param name="fdcPumpControllers">The Fdc pump instance collection.</param>
        private async void InitPumpData(IEnumerable<IFdcPumpController> fdcPumpControllers)
        {
            var pumps = fdcPumpControllers.OrderBy(c => c.PumpId);
            var payableSaleTrxs = await fdcServerHostApp.GetAvailableFuelSaleTrxsWithDetailsAsync(-1);

            foreach (var pump in pumps)
            {
                var pumpNozzles = pump.Nozzles.OrderBy(n => n.LogicalId);
                foreach (var nozzle in pumpNozzles)
                {
                    int nozzleNo = nozzles[(pump.PumpId, nozzle.LogicalId)];

                    var fuelPointTotals = await fdcServerHostApp.GetFuelPointTotalsAsync(pump.PumpId, nozzle.LogicalId);
                    DevicesConfig.pumpDatas[nozzleNo] = new PumpData()
                    {
                        SiteNozzleNumber = nozzleNo,
                        PumpState = "Disconnected",
                        VolumeTotalizer = fuelPointTotals.Item2,
                        PayableTrxsCount = payableSaleTrxs.Where(t => t.PumpId == pump.PumpId && t.LogicalNozzleId == nozzle.LogicalId).Count()
                    };
                }
            }
        }

        #endregion

        #region Event handlers

        private string FdcServerHostApp_OnGetAuthToken(object value)
        {
            var user = JsonConvert.DeserializeObject<User>(value.ToString());
            var tokenTask = Client.Default.GetAuthToken(AuthServiceBaseUrl + "token", user.userName, user.password);

            return tokenTask.Result;
        }

        private bool FdcServerHostApp_OnChangeFuelPrice(object value)
        {
            var fuelProducts = JsonConvert.DeserializeObject<Dictionary<int, FuelProduct>>(value.ToString());
            var succeedNozzles = new List<LogicalNozzle>();
            foreach (FuelProduct val in fuelProducts.Values)
            {
                if (val.NewPrice != null)
                {
                    string deviceSn = "V105182A51890";

                    var posItem = new PosItem();
                    posItem.IsFuelItem = true;
                    posItem.BarCode = val.Barcode.ToString();
                    posItem.ItemId = posItem.BarCode;
                    posItem.ItemName = val.FuelName;
                    posItem.Price = decimal.Parse(val.NewPrice.ToString());

                    var response = Client.Default.ChangeFuelPrice(TransactionServiceBaseUrl + "api/products/fuelgrades", deviceSn, posItem);

                    int barcode = val.Barcode;
                    double newPriceWithDecimalPoints = (double)val.NewPrice;
                    var logicalNozzles = fdcServerHostApp.ChangeFuelPriceAsync(barcode, newPriceWithDecimalPoints);
                    if (logicalNozzles.Result != null)
                        succeedNozzles.AddRange(logicalNozzles.Result);

                    //var pumps = fdcServerHostApp.FdcPumpControllers.OrderBy(c => c.PumpId);
                    //foreach (var pump in pumps)
                    //{
                    //    if (pump.GetType().FullName == "Global_Pump_Fdc.PumpHandler")
                    //    {
                    //        pump.ChangeFuelPriceAsync((int)(newPriceWithDecimalPoints * 1000 + 10000), (byte)barcode);
                    //        break;
                    //    }
                    //}
                }
            }

            string result = succeedNozzles.Aggregate(
                "(PumpId, LogicalId) =>" + Environment.NewLine,
                (str, item) => str += ("(" + item.PumpId + ", " + item.LogicalId + ")-"),
                str => str.Substring(0, str.Length - 1)
                );
            InfoLog($"The successfully price changed nozzle: {result}");
            return succeedNozzles.Count > 0;
        }

        private bool FdcServerHostApp_OnChangeFuelProduct(object value)
        {
            var succeedNozzles = new List<LogicalNozzle>();
            //Client.Default.UploadDataAsync(null, null, AuthServiceBaseUrl);
            return false;
        }

        private void FdcPumpController_OnStateChange(object sender, FdcPumpControllerOnStateChangeEventArg e)
        {
            var currentPump = sender as IFdcPumpController;

            if (currentPump != null)
            {
                try
                {
                    switch (e.NewPumpState)
                    {
                        case LogicalDeviceState.FDC_OFFLINE:
                            foreach (var noz in currentPump.Nozzles)
                            {
                                DevicesConfig.pumpDatas[nozzles[(currentPump.PumpId, noz.LogicalId)]].PumpState = "Disconnected";
                            }
                            break;
                        case LogicalDeviceState.FDC_READY:
                            if (e.StateChangedNozzles != null)
                            {
                                break;
                            }
                            foreach (var noz in currentPump.Nozzles)
                            {
                                DevicesConfig.pumpDatas[nozzles[(currentPump.PumpId, noz.LogicalId)]].PumpState = "Idle";
                            }
                            break;
                        case LogicalDeviceState.FDC_CALLING:
                            if (e.StateChangedNozzles != null)
                            {
                                DevicesConfig.pumpDatas[nozzles[(currentPump.PumpId, e.StateChangedNozzles.First().LogicalId)]].PumpState = "Calling";
                            }
                            break;
                        case LogicalDeviceState.FDC_AUTHORISED:
                            if (e.StateChangedNozzles != null)
                            {
                                DevicesConfig.pumpDatas[nozzles[(currentPump.PumpId, e.StateChangedNozzles.First().LogicalId)]].PumpState = "Authorised";
                                break;
                            }
                            foreach (var noz in currentPump.Nozzles)
                            {
                                DevicesConfig.pumpDatas[nozzles[(currentPump.PumpId, noz.LogicalId)]].PumpState = "Authorised";
                            }
                            break;
                        case LogicalDeviceState.FDC_FUELLING:
                            if (e.StateChangedNozzles != null)
                            {
                                DevicesConfig.pumpDatas[nozzles[(currentPump.PumpId, e.StateChangedNozzles.First().LogicalId)]].PumpState = "Fueling";
                            }
                            break;
                        default:
                            return;
                    }
                }
                catch (Exception ex)
                {
                    ErrorLog(currentPump.PumpId, $"Exception: {ex}");
                }
            }
            else
            {
                InfoLog($"Sender is not an IFdcPumpController, PumpStateChange, New State: {e.NewPumpState}");
                return;
            }
        }

        private async void FdcPumpController_OnCurrentFuellingStatusChange(object sender, FdcServerTransactionDoneEventArg e)
        {
            var currentPump = sender as IFdcPumpController;

            if (currentPump != null)
            {
                try
                {
                    int key = nozzles[(currentPump.PumpId, e.Transaction.Nozzle.LogicalId)];
                    if (e.Transaction.Finished)
                    {
                        DevicesConfig.pumpDatas[key].PumpState = "Idle";
                        DevicesConfig.pumpDatas[key].Amount = e.Transaction.Amount / Math.Pow(10, currentPump.AmountDecimalDigits);
                        DevicesConfig.pumpDatas[key].Volumn = e.Transaction.Volumn / Math.Pow(10, currentPump.VolumeDecimalDigits);
                        DevicesConfig.fuelProducts[e.Transaction.Barcode].CurrentPrice = e.Transaction.Price / Math.Pow(10, currentPump.PriceDecimalDigits);
                        DevicesConfig.pumpDatas[key].PayableTrxsCount++;
                        await TransactionMonitor(currentPump.PumpId, e.Transaction.Nozzle.LogicalId);
                    }
                    else
                    {
                        DevicesConfig.pumpDatas[key].PumpState = "Fueling";
                        DevicesConfig.pumpDatas[key].Amount = e.Transaction.Amount / Math.Pow(10, currentPump.AmountDecimalDigits);
                        DevicesConfig.pumpDatas[key].Volumn = e.Transaction.Volumn / Math.Pow(10, currentPump.VolumeDecimalDigits);
                    }
                }
                catch (Exception ex)
                {
                    ErrorLog(currentPump.PumpId, $"Exception: {ex}");
                }
            }
            else
            {
                InfoLog($"Sender is not an IFdcPumpController, FuellingStateChange, New State: {e.Transaction}");
                return;
            }
        }

        public void FdcPumpController_OnFdcFuelSaleTransactinStateChange(object sender, FdcFuelSaleTransactinStateChangeEventArg e)
        {
            try
            {
                if (e.State == Edge.Core.Database.Models.FuelSaleTransactionState.Paid)
                {
                    DevicesConfig.pumpDatas[nozzles[(e.Transaction.PumpId, e.Transaction.LogicalNozzleId)]].PayableTrxsCount--;
                }
            }
            catch (Exception ex)
            {
                logger.LogError($"OnFdcFuelSaleTransactinStateChange Exception: {ex}");
            }
        }

        #endregion

        #region Log methods

        private void InfoLog(string log)
        {
            if (logger.IsEnabled(LogLevel.Information))
                logger.LogInformation(log);
        }

        private void InfoLog(int pumpId, string log)
        {
            if (logger.IsEnabled(LogLevel.Information))
                logger.LogInformation($"Pump {pumpId}, {log}");
        }

        private void DebugLog(int pumpId, string log)
        {
            if (logger.IsEnabled(LogLevel.Debug))
                logger.LogDebug($"Pump {pumpId}, {log}");
        }

        private void ErrorLog(int pumpId, string log)
        {
            if (logger.IsEnabled(LogLevel.Error))
                logger.LogError($"Pump {pumpId}, {log}");
        }

        #endregion
    }

    public class Test
    {
        int len = 0;
        int[] trxs = { 4, 3, 2, 1, 0 };
        string[] states = { "Fueling", "Disconnected", "Idle", "Calling", "Authorised" };
        double[] amounts = { 116, 116.8, 116.98, 18, 0 };
        double[] volumeTotalizers = { 1169869, 1169869.8, 1169869.98, 18, 0 };

        public Test(int length)
        {
            len = length;
            for (int i = 1; i <= len; i++)
            {
                DevicesConfig.pumpDatas[i] = new PumpData()
                {
                    SiteNozzleNumber = i,
                    PumpState = "Disconnected"
                };
            }

            var timer = new System.Timers.Timer(1000);
            timer.Elapsed += new System.Timers.ElapsedEventHandler(TransactionFileMonitor_newTableEvent);
            timer.Start();
        }

        void TransactionFileMonitor_newTableEvent(object sender, EventArgs e)
        {
            var ran = new Random();
            for (int i = 1; i <= len; i++)
            {
                int randKey = ran.Next(0, 5);
                DevicesConfig.pumpDatas[i].PayableTrxsCount = trxs[randKey];
                DevicesConfig.pumpDatas[i].PumpState = states[randKey];
                DevicesConfig.pumpDatas[i].Amount = amounts[randKey];
                DevicesConfig.pumpDatas[i].VolumeTotalizer = volumeTotalizers[randKey];
            }

            //var timer = sender as System.Timers.Timer;
            //timer.Stop();
            //Trace.WriteLine(DateTime.Now);
        }
    }
}