using Edge.Core.Processor;
using Edge.Core.Processor.Dispatcher.Attributes;
using Edge.Core.UniversalApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using VBaoProxyApp.Cloud;

namespace VBaoProxyApp
{
    [UniversalApi(Name = OnFuelTrxDoneEventName, EventDataType = typeof(object), Description = "使用短码授权加油的用户完成加油时,将触发此事件")]
    [UniversalApi(Name = "OnRemotePaymentFailed", EventDataType = typeof(object), Description = "当与云端进行支付流程进行中,且失败了,将触发此事件")]
    [MetaPartsDescriptor(
        "V宝车载定位",
        "用于与V宝车载定位站级系统的对接",
        new[] { "V宝" })]
    public class App : IAppProcessor
    {
        public const string OnFuelTrxDoneEventName = "OnFuelTrxDone";

        private IServiceProvider services;
        private Applications.FDC.FdcServerHostApp fdcServerApp;
        private AppConfigV1 appConfig;
        private TimeSpan InRangeBeaconLifeTime = new TimeSpan(0, 1, 30);
        private ILogger logger = NullLogger.Instance;

        private object syncObject = new object();
        public class AppConfigV1
        {
            public string UserName { get; set; } = "3038";

            public string Password { get; set; } = "111111";

            /// <summary>
            /// Sample:  http://url:8698
            ///     DO NOT end with slash
            /// </summary>
            public string ApiGatewayEntryUrlWithoutLastSlash { get; set; } = "http://47.97.120.160:8698";


            public string DeviceSN { get; set; } = "66662e74-d51e-42ae-bc89-2d39312c9c30";
        }

        public class InRangeBeacon
        {
            public string BeaconId { get; set; }
            public DateTime Timestamp { get; set; }
            public string AuthCode { get; set; }
        }

        private ConcurrentDictionary<string, InRangeBeacon> inRangeBeacons = new ConcurrentDictionary<string, InRangeBeacon>();
        public string MetaConfigName { get; set; }

        public void Init(IEnumerable<IProcessor> processors)
        {
            this.fdcServerApp =
                   processors.WithHandlerOrApp<Applications.FDC.FdcServerHostApp>().SelectHandlerOrAppThenCast<Applications.FDC.FdcServerHostApp>().FirstOrDefault();
            if (this.fdcServerApp == null)
            {
                this.logger.LogInformation("There's no FdcServerHostApp defined");
                throw new ArgumentException("Must provide FdcServerHostApp");
            }

            //this.fdcServerApp.OnFdcFuelSaleTransactinStateChange += async (s, a) =>
            //{
            //    if (a.State == Edge.Core.Database.Models.FuelSaleTransactionState.Payable)
            //    {
            //        var trxes = await this.fdcServerApp.GetFuelSaleTrxDetailsAsync(new ApiData()
            //        {
            //            Parameters = new List<ApiDataParameter>() {
            //                new ApiDataParameter()
            //                {
            //                    Name="releasetoken",
            //                    Value = a.Transaction.ReleaseToken.ToString()
            //                }
            //            }
            //        });

            //        var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
            //        await universalApiHub.FireEvent(this, OnFuelTrxDoneEventName, new
            //        {
            //            InRangeBeacon = new InRangeBeacon()
            //            {
            //                AuthCode = new Random().Next(0, 9999).ToString().PadLeft(4, '0'),
            //                BeaconId = new Random().Next(99999, 999999999).ToString().PadLeft(10, '0'),
            //            },
            //            FuelTrx = trxes.FirstOrDefault()
            //        });
            //    }
            //};
        }

        public App(AppConfigV1 appConfig, IServiceProvider services)
        {
            this.appConfig = appConfig;
            var loggerFactory = services.GetRequiredService<ILoggerFactory>();
            this.logger = loggerFactory.CreateLogger("DynamicPrivate_VBaoProxyApp");
            this.services = services;
            CloudHelper.Default.Credential = new CloudCredential()
            {
                ApiGatewayEntryUrl = appConfig.ApiGatewayEntryUrlWithoutLastSlash,// "http://wc.shaojun.xyz:8698",
                UserName = appConfig.UserName,// "507",
                Password = appConfig.Password,//"111111",
                DeviceSN = appConfig.DeviceSN,//"1234567890sss",
            };
        }

        public async Task<bool> Start()
        {
            var extraInfos = this.fdcServerApp.GetNozzleExtraInfos();
            var fuelItemBarcodes = extraInfos.GroupBy(i => i.ProductBarcode).Select(g => g.Key);
            foreach (var barcode in fuelItemBarcodes)
            {
                try
                {
                    var cloudFuelItem = await CloudHelper.Default.GetPosItemAsync(barcode.ToString());
                    if (cloudFuelItem == null)
                        throw new InvalidOperationException($"Local fuel item with barcode: {barcode} could not find mapping item in cloud side.");
                }
                catch (Exception exxx)
                {
                    this.logger.LogError($"Failed to retrieve Cloud side fuel item based on local barcode: {barcode}, make sure local fuel items are all" +
                        $" correctly set and mapped(by their item id) in cloud side. error detail: {exxx}");
                    throw;
                }
            }

            return true;
        }

        public Task<bool> Stop() { return Task.FromResult(true); }


        [UniversalApi(Description = "报告用户信标进入了感应范围,并获取验证码短码")]
        public async Task<InRangeBeacon> NotifyBeaconInRange(string userBeaconId)
        {
            this.logger.LogDebug($"NotifyBeaconInRange: {userBeaconId}");
            try
            {
                if (!this.inRangeBeacons.TryGetValue(userBeaconId, out InRangeBeacon exist)
                    || (DateTime.Now.Subtract(exist.Timestamp) > this.InRangeBeaconLifeTime))
                {
                    //remove the expired one
                    if (exist != null)
                    {
                        this.logger.LogDebug($"    removing an expired one...");
                        this.inRangeBeacons.TryRemove(userBeaconId, out _);
                    }

                    string authCode = null;
                    while (true)
                    {
                        authCode = new Random().Next(0, 9999).ToString().PadLeft(4, '0');
                        if (this.inRangeBeacons.Values.Any(i => i.AuthCode == authCode))
                            continue;
                        break;
                    }

                    var info = new InRangeBeacon()
                    {
                        BeaconId = userBeaconId,
                        Timestamp = DateTime.Now,
                        AuthCode = authCode,
                    };
                    this.logger.LogDebug($"    adding one with beaconId: {userBeaconId}, authCode: {authCode}");
                    this.inRangeBeacons.TryAdd(userBeaconId, info);
                    return info;
                    //var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
                    //await universalApiHub.FireEvent(this, OnUserAuthCodeGeneratedEventName, info);
                }
                else
                    return exist;
            }
            finally
            {
                //if (lockTaken) spinLock_NotifyInRangeBeacon.Exit(false);
            }
        }

        [UniversalApi(Description = "验证短码是否有效")]
        public async Task<object> ValidateAuthCode(string authCode)
        {
            bool lockTaken = false;
            try
            {
                var exist = this.inRangeBeacons.FirstOrDefault(i => i.Value.AuthCode == authCode);
                if (exist.Value != null && (DateTime.Now.Subtract(exist.Value.Timestamp) <= this.InRangeBeaconLifeTime))
                {
                    var accountResult = await CloudHelper.Default.GetAccountIdByVBaoBeaconIdAsync(exist.Value.BeaconId);
                    var accountId = accountResult.GetProperty("userId").GetString();
                    var vehicleLicenseNo = accountResult.GetProperty("vehicleLicenseNo").GetString();
                    if (string.IsNullOrEmpty(accountId))
                    {
                        return new
                        {
                            OverallResult = false,
                            FailedReason = $"Could not find account id from remote by vbao beaconId: {exist.Value.BeaconId}, need pre-regist."
                        };
                    }
                    return new
                    {
                        OverallResult = true,
                        VehicleLicenseNo = vehicleLicenseNo,
                    };
                }
                if (exist.Value != null)
                {
                    this.logger.LogDebug($"Validating authCode: {authCode}, but it was expired.");
                    this.inRangeBeacons.TryRemove(exist.Key, out _);
                }

                this.logger.LogDebug($"Validating authCode: {authCode} with failed.");
                return new
                {
                    OverallResult = false,
                    FailedReason = "无效的短码"
                };
            }
            catch (Exception eeee)
            {
                return new
                {
                    OverallResult = false,
                    FailedReason = $"Unknown reason for retrieve the car plate No. from remote, detail: {eeee}",
                };
            }
            finally
            {
                //if (lockTaken) spinLock_NotifyInRangeBeacon.Exit(false);
            }
        }

        [UniversalApi(Description = "使用掉authcode,并提供相关联的fuel trx release token")]
        public async Task<object> NotifyAuthCodeConsumedWithFuelTrx(string authCode, int fuelTrxReleaseToken)
        {
            bool lockTaken = false;
            try
            {
                //spinLock_NotifyInRangeBeacon.Enter(ref lockTaken);
                var exist = this.inRangeBeacons.FirstOrDefault(i => i.Value.AuthCode == authCode);
                if (exist.Value == null)
                {
                    this.logger.LogDebug($"Consuming authCode: {authCode} failed due to no such auth code.");
                    return new
                    {
                        OverallResult = false,
                        FailedReason = $"Consuming authCode: {authCode} failed due to no such auth code."
                    };
                }

                this.logger.LogDebug($"Consuming authCode: {authCode}, then GetFuelSaleTrxDetails for releaseToken: {fuelTrxReleaseToken}");

                var trxes = await this.fdcServerApp.GetFuelSaleTrxDetailsAsync(new ApiData()
                {
                    Parameters = new List<ApiDataParameter>() {
                        new ApiDataParameter()
                        {
                            Name="releasetoken",
                            Value = fuelTrxReleaseToken.ToString()
                        }
                    }
                });

                if (trxes == null || !trxes.Any())
                {
                    //this.logger.LogDebug($"     trxes details retrieved, count: {trxes?.Count() ?? -1}");
                    this.logger.LogDebug($"Consuming authCode: {authCode} failed due to no such fule trx in local.");
                    return new
                    {
                        OverallResult = false,
                        FailedReason = $"Consuming authCode: {authCode} partial failed(code consumed) due to no such fule trx."
                    };
                }


                var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();

                var pumpId = int.Parse(trxes.First().GetType().GetProperty("PumpId").GetValue(trxes.First()).ToString());
                var logicalNozzleId = int.Parse(trxes.First().GetType().GetProperty("LogicalNozzleId").GetValue(trxes.First()).ToString());
                var trxSeqNoFromPhysicalPump = int.Parse(trxes.First().GetType().GetProperty("TransactionSeqNumberFromPhysicalPump").GetValue(trxes.First()).ToString());
                var fuelItemBarcode = trxes.First().GetType().GetProperty("ProductBarcode").GetValue(trxes.First()).ToString();
                var amount = decimal.Parse(trxes.First().GetType().GetProperty("Amount").GetValue(trxes.First()).ToString());
                var volume = decimal.Parse(trxes.First().GetType().GetProperty("Volume").GetValue(trxes.First()).ToString());
                var price = decimal.Parse(trxes.First().GetType().GetProperty("Price").GetValue(trxes.First()).ToString());
                var siteLevelNozzleId = int.Parse(trxes.First().GetType().GetProperty("SiteLevelNozzleId").GetValue(trxes.First()).ToString());

                this.logger.LogDebug($"     local fuel trx details-> pumpId: {pumpId}, logicalNozzleId: {logicalNozzleId}, amount: {amount}, volume: {volume}");

                var fdcClientId = 987654;
                var lockedFuelTrx = await this.fdcServerApp.LockFuelSaleTrxAndNotifyAllFdcClientsAsync(
                    fdcClientId, pumpId, trxSeqNoFromPhysicalPump, fuelTrxReleaseToken);
                if (lockedFuelTrx == null)
                {
                    this.logger.LogError($"Local locking trx with releaseToken: {fuelTrxReleaseToken} failed.");
                    await universalApiHub.FireEvent(this, "OnRemotePaymentFailed", new
                    {
                        FuelTrx = trxes.FirstOrDefault(),
                        FailedReason = $"Local fuel trx with releaseToken: {fuelTrxReleaseToken} locking failed."
                    });
                    return new
                    {
                        OverallResult = false,
                        FailedReason = $"Local fuel trx with releaseToken: {fuelTrxReleaseToken} locking failed."
                    };
                }

                var cloudFuelItem = await CloudHelper.Default.GetPosItemAsync(fuelItemBarcode);
                if (cloudFuelItem == null)
                {
                    this.logger.LogError($"Retreieve cloud pos item failed for fuel trx with releaseToken: {fuelTrxReleaseToken} with fuel item barcode: {fuelItemBarcode}");
                    await universalApiHub.FireEvent(this, "OnRemotePaymentFailed", new
                    {
                        FuelTrx = trxes.FirstOrDefault(),
                        FailedReason = $"Local fuel item with barcode: {fuelItemBarcode} could not find mapping item in cloud side."
                    });
                    return new
                    {
                        OverallResult = false,
                        FailedReason = $"Local fuel item with barcode: {fuelItemBarcode} could not find mapping item in cloud side."
                    };
                }

                var createdTrxId = await CloudHelper.Default.CreateTransactionAsync(new ClientFuelTrxInfo()
                {
                    PosItemId = cloudFuelItem.Id,
                    PumpId = pumpId,
                    NozzleId = logicalNozzleId,
                    SiteNozzleNo = siteLevelNozzleId,
                    Amount = amount,
                    Volume = volume,
                    Barcode = int.Parse(cloudFuelItem.BarCode),
                    PayAmount = amount,
                    Source = Dfs.WayneChina.PosModelMini.PosTrxSource.Outdoor,
                    UnitPrice = price
                }, "vBao pay");

                var accountResult = await CloudHelper.Default.GetAccountIdByVBaoBeaconIdAsync(exist.Value.BeaconId);
                var accountId = accountResult.GetProperty("userId").GetString();
                var vehicleLicenseNo = accountResult.GetProperty("vehicleLicenseNo").GetString();
                if (string.IsNullOrEmpty(accountId))
                {
                    await universalApiHub.FireEvent(this, "OnRemotePaymentFailed", new
                    {
                        FuelTrx = trxes.FirstOrDefault(),
                        FailedReason = $"Could not find account id from remote by vbao beaconId: {exist.Value.BeaconId}, need pre-regist."
                    });
                    return new
                    {
                        OverallResult = false,
                        FailedReason = $"Could not find account id from remote by vbao beaconId: {exist.Value.BeaconId}, need pre-regist."
                    };
                }

                var commitResult = await CloudHelper.Default.CommitTransactionAsync(createdTrxId, new Dfs.WayneChina.PosModelMini.PosTrxMop()
                {
                    Mop = new Dfs.WayneChina.PosModelMini.PosMop()
                    {
                        PaymentId = (int)PaymentID.MembershipPay,
                    },
                    Paid = amount
                }, accountId);
                if (!commitResult)
                {
                    this.logger.LogError($"Commit trx to cloud failed for fuel trx with releaseToken: {fuelTrxReleaseToken}");
                    await universalApiHub.FireEvent(this, "OnRemotePaymentFailed", new
                    {
                        FuelTrx = trxes.FirstOrDefault(),
                        FailedReason = $"Commit trx to remote failed with MembershipPay, reason unknown."
                    });
                    return new
                    {
                        OverallResult = false,
                        FailedReason = $"Commit trx to remote failed with MembershipPay, reason unknown."
                    };
                }

                var clearedFuelTrx = await this.fdcServerApp.ClearFuelSaleTrxAndNotifyAllFdcClientsAsync(
                    pumpId, trxSeqNoFromPhysicalPump.ToString(), fuelTrxReleaseToken, fdcClientId.ToString());
                if (clearedFuelTrx == null)
                {
                    this.logger.LogError($"Clear local fuel trx failed for fuel trx with releaseToken: {fuelTrxReleaseToken}");
                    await universalApiHub.FireEvent(this, "OnRemotePaymentFailed", new
                    {
                        FuelTrx = trxes.FirstOrDefault(),
                        FailedReason = $"Local fuel trx with releaseToken: {fuelTrxReleaseToken} clear failed."
                    });
                    return new
                    {
                        OverallResult = false,
                        FailedReason = $"Local fuel trx with releaseToken: {fuelTrxReleaseToken} clear failed."
                    };
                }

                await universalApiHub.FireEvent(this, OnFuelTrxDoneEventName, new
                {
                    InRangeBeacon = new { exist.Value.BeaconId, exist.Value.Timestamp, exist.Value.AuthCode },
                    FuelTrx = trxes.FirstOrDefault()
                });
                this.inRangeBeacons.TryRemove(exist.Key, out _);
                this.logger.LogInformation($"Succeed finished a fuel trx payment with cloud and local with releaseToken: {fuelTrxReleaseToken}");
                return new
                {
                    OverallResult = true,
                    VehicleLicenseNo = vehicleLicenseNo,
                    //FailedReason = "",
                };
            }
            catch (Exception eeee)
            {
                return new
                {
                    OverallResult = false,
                    FailedReason = $"Unknown reason to failed the payment from remote, detail: {eeee}",
                };
            }
            finally
            {
                //if (lockTaken) spinLock_NotifyInRangeBeacon.Exit(false);
            }
        }
    }
}