using SinoChemFC2PosProxy.Communicator;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading;
using Wayne.ForecourtControl;
using Wayne.ForecourtControl.Com;
using Wayne.Fusion.Framework.Core;
using Wayne.Lib;
using Wayne.Lib.Log;
using IPump = Wayne.ForecourtControl.IPump;

namespace SinoChemFC2PosProxy
{
    public delegate void NozzleLiftedHandler(int sitewiseNozzleId, IPump callingPump);
    public delegate void NozzleReplacedHandler(int sitewiseNozzleId);
    public delegate void FuelingDoneHandler(int sitewiseNozzleId, int seqNum, decimal fuelAmount, decimal quantity, long authId);
    public delegate void AuthOkHandler(int sitewiseNozzleId, long? authId);
    public delegate void AuthFailedHandler(int sitewiseNozzleId);

    public class FdcCommunicator : IDisposable, ICommunicator
    {
        public event NozzleLiftedHandler NozzleLifted;
        public event NozzleReplacedHandler NozzleReplaced;
        public event FuelingDoneHandler FuelingDone;
        public event AuthOkHandler AuthOk;
        public event AuthFailedHandler AuthFailed;

        /// <summary>
        /// 0 for not started, 1 for started already.
        /// </summary>
        private int isStarted = 0;

        private const string DEFAULT_FDC_SERVER_CONNECT_STRING = "Host=127.0.0.1,Port=4710,ClientId=101,ClientName=PetroChinaProxy,PortB=4710,PortC=4710";
        private readonly string concreteFdcServerConnString = string.Empty;
        //private readonly DebugLogger debugLogger =
        //       new DebugLogger(new IdentifiableEntity(0, "FC2PosProxyMain", "", null));
        static NLog.Logger debugLogger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("PumpHandler");

        //private readonly Wayne.ForecourtControl.IForecourtControl forecourtControl;
        //private readonly MessageRouterClient msgRouterClient;

        private bool autoAuthorizePumpWhenCalling = false;
        /// <summary>
        /// The Fdc communicator works as a FDC client which connected to FC.
        /// </summary>
        /// <param name="msgRouterClient">somehow, still need to communicate the Message Router</param>
        public FdcCommunicator()
        {
            //if (!String.IsNullOrEmpty(
            //        ConfigurationManager.AppSettings["AutoAuthorizePumpWhenCalling"]))
            //{
            //    this.autoAuthorizePumpWhenCalling =
            //        (ConfigurationManager.AppSettings["AutoAuthorizePumpWhenCalling"].ToLower() ==
            //        "true"
            //            ? true
            //            : false);
            //}

            //var fdcServerIpAddress = ConfigurationManager.AppSettings["FdcServerIpAddress"];
            //this.concreteFdcServerConnString = DEFAULT_FDC_SERVER_CONNECT_STRING.Replace("127.0.0.1", fdcServerIpAddress)
            //    .Replace("ClientId=101", "ClientId=" + ConfigurationManager.AppSettings["ClientId"]);
            //this.forecourtControl = Wayne.ForecourtControl.Fusion.FUSIONFactory.CreateForecourtControl(0);
            //this.forecourtControl.OnConnectionStateChange += forecourtControl_OnConnectionStateChange;

            //this.msgRouterClient = msgRouterClient;
            //this.msgRouterClient.Start();
        }

        //void forecourtControl_OnConnectionStateChange(object sender, ConnectionChangedEventArgs e)
        //{
        //    debugLogger.Add("forecourtControl_OnConnectionStateChange(), new state: " + e.ConnectionState, DebugLogLevel.Normal);
        //    if (e.ConnectionState == Wayne.Lib.DeviceConnectionState.Disconnected)
        //    {
        //        foreach (var pump in this.forecourtControl.Pumps)
        //        {
        //            pump.OnFuellingStateChange -= FdcCommunicator_OnFuellingStateChange;
        //            pump.OnNozzleStateChange -= FdcCommunicator_OnNozzleStateChange;
        //            pump.OnEventOccured -= FdcCommunicator_OnEventOccured;
        //        }
        //    }
        //    else if (e.ConnectionState == Wayne.Lib.DeviceConnectionState.Connected)
        //    {
        //        // sometimes could not receive any notification from FDC server even attached the event handler, 
        //        // suspect some underlying bug in communication layer, so here try sleep a while to avoid(probably) this.
        //        //
        //        Thread.Sleep(500);
        //        foreach (var pump in this.forecourtControl.Pumps)
        //        {
        //            pump.OnFuellingStateChange += FdcCommunicator_OnFuellingStateChange;
        //            pump.OnNozzleStateChange += FdcCommunicator_OnNozzleStateChange;
        //            pump.OnEventOccured += FdcCommunicator_OnEventOccured;
        //        }

        //        const int maxRetryTimes = 10;
        //        int retriedTimes = 0;
        //        while (!this.msgRouterClient.SendMessage(MsgRouterMessageUtility.RefreshPumpStatus()))
        //        {
        //            if (++retriedTimes > maxRetryTimes) break;
        //            debugLogger.Add("failed to send RefreshPumpStatus() to MsgRouterServer, will keep retrying until max times reached...", DebugLogLevel.Normal);
        //            Thread.Sleep(2000);
        //        }

        //        this.forecourtControl.SetSiteOpenedAsync(true, (_, __) => { }, null);
        //    }
        //}

        //void FdcCommunicator_OnEventOccured(object sender, Wayne.ForecourtControl.PumpEventOccuredEventArgs e)
        //{
        //    debugLogger.Add("FdcCommunicator_OnEventOccured(), args: " + e, DebugLogLevel.Maximized);
        //}

        void FdcCommunicator_OnNozzleStateChange(object sender, Wayne.ForecourtControl.NozzleStateChangeEventArgs e)
        {
            var callingPump = sender as IPump;
            debugLogger.Debug("FdcCommunicator_OnNozzleStateChange(), args: pumpId: " + callingPump.Id + " nozzleId: " + e.Nozzle.Id + ", newState: " + e.NozzleState);
            int sitewiseNozzleId = SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(callingPump.Id, e.Nozzle.Id);
            if (e.NozzleState == NozzleState.In)
            {
                /* indicate for nozzle if replaced back */
                var sizeLevelNozzleIdsOnPump = SiteConfigUtility.Default.GetSiteLevelNozzleIdsByPumpId(callingPump.Id);
                if (!sizeLevelNozzleIdsOnPump.Any())
                {
                    debugLogger.Debug("Could not found any site level nozzle ids for pump: " + callingPump.Id);
                    return;
                }

                using (var posSqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["PosDatabaseConnStr"].ConnectionString))
                {
                    try
                    {
                        /* idle would not carry nozzle id, so here reset all nozzles on target pump.*/
                        var setPumpOnIdleCommand
                            = new SqlCommand(sizeLevelNozzleIdsOnPump.Select(siteLevelNozzleId =>
                            {
                                var totalizer = SiteConfigUtility.Default.GetTotalizer(siteLevelNozzleId);
                                return
                                    string.Format(
                                        "Update jy_info set [status] = '{1}', qty=0, amount=0, fzqty='{2}', fzamount={3}" +
                                        " where jihao = {0}", siteLevelNozzleId, 'F',
                                        totalizer.Item1, totalizer.Item2);
                            })
                                .Aggregate((acc, n) => acc + "   " + n), posSqlConnection);
                        debugLogger.Add("setPumpOnIdleCommand(via Fdc): " + setPumpOnIdleCommand.CommandText, DebugLogLevel.Maximized);
                        posSqlConnection.Open();
                        setPumpOnIdleCommand.ExecuteNonQuery();
                    }
                    catch (Exception ex)
                    {
                        debugLogger.Add("executing setPumpOnIdleCommand(via Fdc) failed, exception detail: " + ex,
                            DebugLogLevel.Normal);
                    }
                }

                NozzleReplaced?.Invoke(sitewiseNozzleId);
            }
            else if (e.NozzleState == NozzleState.Out)
            {
                debugLogger.Add("fire nozzle out event");
                NozzleLifted?.Invoke(sitewiseNozzleId, callingPump);
            }
        }

        public void AuthorizePumpAsync(IPump callingPump, int sitewiseNozzleId, decimal authAmount)
        {
            if (this.autoAuthorizePumpWhenCalling)
            {
                var authParameter = new AuthorizeParameters()
                {
                    PriceGroup = PriceGroup.FullService,
                    LockToReleaseClient = false,
                    PresetType = PresetType.Amount,
                    PresetValue = authAmount,
                    Prepay = false,
                    PayType = "PC",
                };
                for (int i = 0; i < callingPump.Nozzles.Count; i++)
                {
                    int idFuelGrade = callingPump.Nozzles[i].FuelGrade;
                    authParameter.AllowedFuelGrade[idFuelGrade] = true;
                }

                debugLogger.Add(
                            "Authorizing for pumpId: " + callingPump.Id + ",  authAmount:" + authAmount,
                            DebugLogLevel.Normal);
                callingPump.AuthorizeAsync(authParameter, (_, arg) =>
                {
                    var pumpId = (int)(arg.UserToken);
                    if (arg.Success)
                    {       
                        debugLogger.Add(
                            "AuthorizeAsync finished successfully for pumpId: " + pumpId,
                            DebugLogLevel.Detailed);
                        AuthOk?.Invoke(sitewiseNozzleId, arg.Result);
                    }
                    else
                    {
                        debugLogger.Add(
                            "AuthorizeAsync failed for pumpId: " + pumpId,
                            DebugLogLevel.Normal);
                        AuthFailed?.Invoke(sitewiseNozzleId);
                    }
                }, callingPump.Id);
            }
            else
            {
                debugLogger.Add(
                            "No need to auth before fueling for pumpId: " + callingPump.Id,
                            DebugLogLevel.Normal);
                AuthOk?.Invoke(sitewiseNozzleId, null);
            }
        }

        void FdcCommunicator_OnFuellingStateChange(object sender, Wayne.ForecourtControl.FuellingStateChangeEventArgs e)
        {
            debugLogger.Add("FdcCommunicator_OnFuellingStateChange(), args: " + e, DebugLogLevel.Detailed);
            debugLogger.Add("\r\n PumpID    = " + e.Fuelling.Pump.Id +
                "\r\n Nozzle    = " + e.Fuelling.Nozzle.Id +
                "\r\n Amount    = $" + e.Fuelling.Amount +
                "\r\n State     = " + e.State +
                "\r\n FuelGrade = " + e.Fuelling.FuelGrade +
                "\r\n ReservingDeviceId = " + e.Fuelling.ReservingDeviceId +
                "\r\n Quantity  = " + e.Fuelling.Quantity +
                "\r\n ReservedBy  = " + e.Fuelling.ReservedBy +
                "\r\n FuelPeriodID  = " + e.Fuelling.FuelPeriodId +
                "\r\n FuellingSeqNumber = " + e.Fuelling.FuellingSequenceNumber +
                "\r\n Price     = $" + e.Fuelling.Price);
            if (e.State == FuellingState.PayableTransaction)
            {
                /* in SinoChem project, the pump was set to FullService mode, so the PayableTransaction case here is impossible to happen, but just leave the code here*/
                this.forecourtControl.Pumps[e.Fuelling.Pump.Id - 1].Fuellings.ToList().ForEach(f =>
                {
                    var fsn = f.FuellingSequenceNumber;
                    debugLogger.Add("Sending SetAsPaidAsync for pumpId: " + f.Pump.Id + ", FuellingSequenceNumber: " + fsn,
                                DebugLogLevel.Detailed);
                    f.SetAsPaidAsync((_, arg) =>
                    {
                        var pumpId = (int)(arg.UserToken);
                        if (arg.Success)
                        {
                            debugLogger.Add(
                                "SetAsPaidAsync finished successfully for pumpId: " + pumpId + ", FuellingSequenceNumber: " + fsn,
                                DebugLogLevel.Detailed);
                        }
                        else
                        {
                            debugLogger.Add(
                                "SetAsPaidAsync failed for pumpId: " + pumpId + ", FuellingSequenceNumber: " + fsn,
                                DebugLogLevel.Normal);
                        }
                    }, f.Pump.Id);
                });
            }
            else if (e.State == FuellingState.Paid)
            {
                var posSqlConnection =
                    new SqlConnection(ConfigurationManager.ConnectionStrings["PosDatabaseConnStr"].ConnectionString);
                int sitewiseNozzleId = SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(e.Fuelling.Pump.Id, e.Fuelling.Nozzle.Id);
                using (posSqlConnection)
                {
                    try
                    {
                        var totalizer = SiteConfigUtility.Default.GetTotalizer(e.Fuelling.Pump.Id, e.Fuelling.Nozzle.Id);                         
                        var updateFuelingTrxDoneCommand =
                            new SqlCommand(
                                string.Format(
                                    "insert xiaofei2 (jihao, youpin, qty, danjia, amount, xf_date, xf_time, liushuino, fzqty, fzamount)" +
                                    " values({0}, N'{1}', {2}, {3}, {4}, '{5}', '{6}', '{7}', '{8}', {9})",
                                    sitewiseNozzleId,
                                    Translator.GetFriendlyGradeName(SiteConfigUtility.Default.GetGradeNameByGradeId(e.Fuelling.FuelGrade)),
                                    e.Fuelling.Quantity,
                                    e.Fuelling.Price,
                                    e.Fuelling.Amount,
                                    DateTime.Now.Date.ToString("yyyy-MM-dd"),
                                    DateTime.Now.ToString("HH:mm:ss"),
                                    e.Fuelling.FuellingSequenceNumber,
                                    totalizer.Item1,
                                    totalizer.Item2),
                                posSqlConnection);
                        debugLogger.Add("updateFuelingTrxDoneCommand: " + updateFuelingTrxDoneCommand.CommandText,
                            DebugLogLevel.Maximized);
                        posSqlConnection.Open();
                        updateFuelingTrxDoneCommand.ExecuteNonQuery();
                    }
                    catch (Exception ex)
                    {
                        debugLogger.Add("executing updateFuelingTrxDoneCommand failed, exception detail: " + ex,
                            DebugLogLevel.Normal);
                    }
                }

                FuelingDone?.Invoke
                    (sitewiseNozzleId, e.Fuelling.FuellingSequenceNumber, e.Fuelling.Amount, e.Fuelling.Quantity, e.Fuelling.AuthorizationId);
            }
        }

        public void Dispose()
        {
            this.forecourtControl.Dispose();
            this.debugLogger.Dispose();
        }

        public bool Start()
        {
            if (0 == Interlocked.CompareExchange(ref this.isStarted, 1, 0))
            {
                debugLogger.Add("Connecting to FDC server with connStr: " + this.concreteFdcServerConnString, DebugLogLevel.Normal);
                this.forecourtControl.Connect(this.concreteFdcServerConnString);
                return true;
            }
            else
            {
                throw new InvalidOperationException("Already started.");
            }
        }

        public bool IsStarted
        {
            get { return this.isStarted == 1; }
        }
    }
}