using Dfs.WayneChina.PosModelMini;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace VBaoProxyApp.Cloud
{
    public enum PaymentID
    {
        Cash = 1, //室内现金
        AliPay = 2,
        WechatPay = 3,
        IC = 4,
        MiniProgram = 5,
        ThirdPartyPay = 6,
        AutoCleared = 7, //油机大屏上的自动清除
        BankCard = 8,
        OutdoorCash = 9, //油机大屏的室外现金
        IndoorPay = 10, //油机大屏上的室内支付
        AllInPay = 11, //通联支付,
        PumpTest = 12, //打油测试
        AllInPayV2 = 13, //通联分账支付,
        WechatQrScan = 20, //微信扫码,统一下单,用户扫商户,
        AliPayQrScan = 21, //支付宝扫码,统一下单,用户扫商户,
        CarPlate = 100,
        /// <summary>
        /// outdoor driver drive off without real payment
        /// </summary>
        DriveOff = 400,
        /// <summary>
        /// indoor customer leave off without real payment
        /// </summary>
        LeaveOff = 401,
        /// <summary>
        /// membership depoist balance payment initiated by mini program, 余额支付
        /// </summary>
        MembershipPay = 500,
        /// <summary>
        /// membership mobile payment initiated by mini program, 小程序微信支付
        /// </summary>
        MembershipMobilePay = 501,

        PetroChinaIC = 600,

    }
    public class TrxCreationResponse
    {
        public Guid Id { get; set; }
    }

    public class ServerSideItem
    {
        public string itemId { get; set; }
        public string BarCode { get; set; }
        public string ItemName { get; set; }
        public decimal Price { get; set; }
        public bool IsFuelItem { get; set; }
        public Guid Id { get; set; }
    }

    public class ServerSideTrxItem
    {
        public int LineNum { get; set; }
        public Guid PosItemId { get; set; }
        public ServerSideItem Item { get; set; }
        public double Qty { get; set; }
        public int FuelItemSoldOnPumpId { get; set; }
        public int FuelItemSoldOnPumpNozzleId { get; set; }
        public string FuelItemFdcTransactionSeqNo { get; set; }
        public decimal FuelItemPumpTotalizerVolume { get; set; }

    }
    public enum ServerSideTrxStatus
    {
        Open,
        Finish
    }
    public enum ServerSideTrxType
    {
        Sale = 0,
        Refund = 1,
        Reconciliation = 2,
        Redemption = 3,
        LogOff = 4,
        LogOn = 5,
        EndOfShift = 6,
        EndOfDay = 7,
        Restore = 8,
        FuelDelivery = 9, //Manual input on POS Side
        TankDelivery = 10, //Auto Detect, in using
        TankReading = 11 //when POS EOS, POS Read ATG Data
    }
    public class ServerSideTrx
    {
        public Guid Id { get; set; }
        public ServerSideTrxType TransactionType { get; set; }
        public DateTime CreatedDateTime { get; set; }
        public Guid TargetBusinessUnitId { get; set; }
        public decimal NetAmount { get; set; }
        public decimal GrossAmount { get; set; }
        public List<ServerSideTrxItem> Items { get; set; }
        public ServerSideTrxStatus TransactionStatus { get; set; }
    }

    public class ClientFuelTrxInfo
    {
        public PosTrxSource Source { get; set; }
        public int Barcode { get; set; }
        public int PumpId { get; set; }
        public int NozzleId { get; set; }
        public int SiteNozzleNo { get; set; }

        /// <summary>
        /// should get it from cloud side.
        /// </summary>
        public Guid PosItemId { 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; }
    }
    public class AuthenticationParameter
    {
        public AuthenticationParameter(string grantType, string userName, string password)
        {
            Params.Clear();

            Params.Add("grant_type", grantType);
            Params.Add("username", userName);
            Params.Add("password", password);
        }

        public Dictionary<string, string> Params { get; } = new Dictionary<string, string>();
    }
    public class CloudCredential
    {
        public string UserName { get; set; }

        public string Password { get; set; }

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

        //public string AuthServiceBaseUrl { get; set; }

        //public string TransactionServiceBaseUrl { get; set; }

        //public string DiscountServiceBaseUrl { get; set; }

        public string DeviceSN { get; set; }

        //public string CurrentBuId { get; set; }
    }

    //public class PosItem
    //{
    //    /// <summary>
    //    /// Unique id.
    //    /// </summary>
    //    public Guid Id { get; set; }

    //    /// <summary>
    //    /// Item Id.
    //    /// </summary>
    //    public int ItemId { get; set; }

    //    /// <summary>
    //    /// Item barcode.
    //    /// </summary>
    //    public string BarCode { get; set; }

    //    /// <summary>
    //    /// Change set id.
    //    /// </summary>
    //    public Guid ChangesetId { get; set; }

    //    /// <summary>
    //    /// Name of the item.
    //    /// </summary>
    //    public string ItemName { get; set; }

    //    /// <summary>
    //    /// Unit id.
    //    /// </summary>
    //    public int UnitId { get; set; }

    //    /// <summary>
    //    /// Unit price.
    //    /// </summary>
    //    public decimal Price { get; set; }

    //    /// <summary>
    //    /// When the item is activated.
    //    /// </summary>
    //    public DateTime TimeToActivate { get; set; }

    //    /// <summary>
    //    /// When the time is deactivated.
    //    /// </summary>
    //    public DateTime TimeToDeactivate { get; set; }

    //    /// <summary>
    //    /// When the item is created.
    //    /// </summary>
    //    public DateTime CreatedDateTime { get; set; }

    //    /// <summary>
    //    /// Whether this item is a fuel item.
    //    /// </summary>
    //    public bool IsFuelItem { get; set; }

    //    /// <summary>
    //    /// Whether the item is deleted.
    //    /// </summary>
    //    public bool IsMarkedAsDeletion { get; set; }

    //    /// <summary>
    //    /// Target business unit id.
    //    /// </summary>
    //    public Guid TargetBusinessUnitId { get; set; }
    //}
    public class BusinessUnit
    {
        [JsonProperty("Id")]
        public Guid Id { get; set; }

        [JsonProperty("Name")]
        public string Name { get; set; }
    }

    public class AuthToken
    {

        #region Standard OAuth token format

        /// <summary>
        /// The access token string issued by the authorization server.
        /// </summary>
        [JsonProperty("access_token")]
        public string AccessToken { get; set; }

        /// <summary>
        /// Token type, simply `bearer`.
        /// </summary>
        [JsonProperty("token_type")]
        public string TokenType { get; set; }

        /// <summary>
        /// Duration of the time the access token is granted for, in total seconds.
        /// </summary>
        [JsonProperty("expires_in")]
        public int ExpiresIn { get; set; }

        /// <summary>
        /// Access token to be used to get a new token, optional.
        /// </summary>
        [JsonProperty("refresh_token")]
        public string RefreshToken { get; set; }

        /// <summary>
        /// The error information
        /// </summary>
        [JsonProperty("error")]
        public string Error { get; set; }

        [JsonProperty("userName")]
        public string UserName { get; set; }

        [JsonProperty("alias")]
        public string UserAlias { get; set; }

        [JsonProperty("roleNames")]
        public string RoleNames { get; set; }

        [JsonProperty("BusinessUnits")]
        public string BusinessUnitsJsonString { get; set; }

        public IList<BusinessUnit> BusinessUnitList { get; set; }

        #endregion

        #region Custom extension

        /// <summary>
        /// When the token is retrieve from the auth server, to simplify, set the local received time.
        /// </summary>
        public DateTime TokenRetrievedTime { get; set; }

        /// <summary>
        /// Check if the token has expired or not, if not, then it's valid.
        /// </summary>
        /// <returns>valid or not</returns>
        public bool IsTokenValid()
        {
            //Current Time = Now + 5 minutes, Expiry Time = token retrieved time + expiry time span
            //If current time is not later than expiry time, then the token is valid
            if (DateTime.Now + new TimeSpan(0, 5, 0) < TokenRetrievedTime + new TimeSpan(0, 0, ExpiresIn))
                return true;

            return false;
        }

        #endregion
    }
    public class CloudHelper
    {

        public CloudCredential Credential { get; set; }

        public ILogger Logger { get; set; } = NullLogger.Instance;


        public int Id { get; }

        private string grantType = "password";
        private string tokenAuthPath = "token";
        private string authScheme = "bearer";

        private HttpClient _client = new HttpClient();

        private AuthToken currentAuthToken = null;


        private static CloudHelper instance = new CloudHelper();
        public static CloudHelper Default { get; } = instance;
        private CloudHelper()
        {
        }


        private async Task RefreshAuthTokenAsync(string userName, string password)
        {
            this.Logger.LogDebug("GetTokenAsync...");

            _client.DefaultRequestHeaders.Clear();
            string tokenUrl = string.Concat(this.Credential.ApiGatewayEntryUrl, "/", tokenAuthPath);
            var formParam = new AuthenticationParameter(grantType, userName, password);

            var response = await _client.PostAsync(tokenUrl, new FormUrlEncodedContent(formParam.Params)).ConfigureAwait(false);
            this.Logger.LogDebug($"Response for getting token, Response==null? {response == null}");
            if (response != null && response.IsSuccessStatusCode)
            {
                var resultStr = await response.Content.ReadAsStringAsync();
                this.currentAuthToken = JsonConvert.DeserializeObject<AuthToken>(resultStr);
                if (!string.IsNullOrEmpty(this.currentAuthToken.BusinessUnitsJsonString))
                {
                    this.currentAuthToken.BusinessUnitList = JsonConvert.DeserializeObject<IList<BusinessUnit>>(currentAuthToken.BusinessUnitsJsonString);
                    return;
                }
            }

            throw new InvalidOperationException($"GetAuthTokenAsync failed(serverUrl: {tokenUrl}, username/pwd: {userName ?? ""}/{password ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
        }

        /// <summary>
        /// query the last trx from server side, if its TransactionStatus == Open, then server will return it. otherwise, server will return null.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public async Task<T> GeLastTrxWithOpenStatusByUserNameAsync<T>()
        {
            if (this.currentAuthToken == null)
                await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);

            string targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/?searchType=lastOpenSaleTrx&searchValue0=userName&searchValue1=", this.Credential.UserName);
            _client.DefaultRequestHeaders.Clear();
            _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, this.currentAuthToken.AccessToken);

            this.Logger.LogDebug($"{_client.DefaultRequestHeaders.Authorization.Scheme}, {_client.DefaultRequestHeaders.Authorization.Parameter}");
            //BusinessUnitId = currentAuthToken.BusinessUnitList.Count > 0 ? currentAuthToken.BusinessUnitList.First().Id.ToString() : string.Empty;

            //Be aware, DeviceSN is `required` after 2019.04 for Smart Fuel v0.5
            _client.DefaultRequestHeaders.Add("DeviceSN", this.Credential.DeviceSN);

            var response = await _client.GetAsync(targetUrl).ConfigureAwait(false);
            if (response != null && response.IsSuccessStatusCode)
            {
                var resultStr = await response.Content.ReadAsStringAsync();
                if (string.IsNullOrEmpty(resultStr)) return default(T);
                var jd = JsonDocument.Parse(resultStr);
                return JsonConvert.DeserializeObject<T>(resultStr);
                //return jd.RootElement.GetProperty("result");
            }

            throw new InvalidOperationException($"GeLastOpenTrxByUserNameAsync failed(serverUrl: {targetUrl}, userName: {this.Credential.UserName ?? ""}, deviceSN: {this.Credential?.DeviceSN ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
        }

        public async Task<PosItem> GetPosItemAsync(string itemId)
        {
            if (this.currentAuthToken == null)
                await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);

            string productItemUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/Products/", "itemId/");
            this.Logger.LogDebug("GetPosItemAsync...");
            _client.DefaultRequestHeaders.Clear();
            _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, this.currentAuthToken.AccessToken);

            this.Logger.LogDebug($"{_client.DefaultRequestHeaders.Authorization.Scheme}, {_client.DefaultRequestHeaders.Authorization.Parameter}");
            //BusinessUnitId = currentAuthToken.BusinessUnitList.Count > 0 ? currentAuthToken.BusinessUnitList.First().Id.ToString() : string.Empty;

            //Be aware, DeviceSN is `required` after 2019.04 for Smart Fuel v0.5
            _client.DefaultRequestHeaders.Add("DeviceSN", this.Credential.DeviceSN);
            //_client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);

            //this.logger.LogDebug($"DeviceSN: {DeviceSN}");
            //this.logger.LogDebug($"BuId: {BusinessUnitId}");

            var productUrl = string.Concat(productItemUrl, itemId);
            this.Logger.LogDebug($"item url: {productUrl}");

            var response = await _client.GetAsync(productUrl).ConfigureAwait(false);
            if (response != null && response.IsSuccessStatusCode)
            {
                var resultStr = await response.Content.ReadAsStringAsync();
                return JsonConvert.DeserializeObject<PosItem>(resultStr);
            }

            throw new InvalidOperationException($"GetPosItemAsync failed(serverUrl: {productItemUrl}, itemId: {itemId}, deviceSN: {this.Credential?.DeviceSN ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
        }

        public async Task<JsonElement> GetAccountIdByVBaoBeaconIdAsync(string beaconId)
        {
            if (this.currentAuthToken == null)
                await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);

            string targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/userinfo/", "vBaoId?vBaoId=", beaconId);
            _client.DefaultRequestHeaders.Clear();
            _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, this.currentAuthToken.AccessToken);

            this.Logger.LogDebug($"{_client.DefaultRequestHeaders.Authorization.Scheme}, {_client.DefaultRequestHeaders.Authorization.Parameter}");
            //BusinessUnitId = currentAuthToken.BusinessUnitList.Count > 0 ? currentAuthToken.BusinessUnitList.First().Id.ToString() : string.Empty;

            //Be aware, DeviceSN is `required` after 2019.04 for Smart Fuel v0.5
            _client.DefaultRequestHeaders.Add("DeviceSN", this.Credential.DeviceSN);

            var response = await _client.GetAsync(targetUrl).ConfigureAwait(false);
            if (response != null && response.IsSuccessStatusCode)
            {
                var resultStr = await response.Content.ReadAsStringAsync();
                var jd = JsonDocument.Parse(resultStr);
                return jd.RootElement.GetProperty("result");
            }

            throw new InvalidOperationException($"GetAccountIdByVBaoBeaconIdAsync failed due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
        }

        public async Task<Guid> CreateTransactionAsync(ClientFuelTrxInfo clientFuelTrxInfo, string trxComment)
        {
            if (this.currentAuthToken == null)
                await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
            Guid trxId = Guid.Empty;

            var trxUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/");
            var clientPosTrx = new ClientPosTrx
            {
                RequestingCreationTimeInPosClient = DateTime.Now,
                Type = PosTrxType.Sale,
                Source = clientFuelTrxInfo.Source,
                Items = new List<ClientRingUpPosItem>
                {
                    new ClientRingUpPosItem
                    {
                        PosItemUniqueId = clientFuelTrxInfo.PosItemId, //GUID
                        Qty = clientFuelTrxInfo.Volume,
                        ClientRingUpTime = DateTime.Now,
                        FuelItemSoldOnPumpId = clientFuelTrxInfo.PumpId,
                        FuelItemSoldOnPumpNozzleId = clientFuelTrxInfo.SiteNozzleNo,//SiteNozzles[(clientTrxInfo.PumpId, clientTrxInfo.NozzleId)],//fdcTrx.Nozzle.LogicalId,
                        FuelItemOriginalGrossAmount = clientFuelTrxInfo.Amount,//(decimal)trdInfo.Mon.Value / 100,
                        FuelItemFdcTransactionSeqNo = Convert.ToString(clientFuelTrxInfo.SeqNo),
                        fuelItemTransactionEndTime = clientFuelTrxInfo.FuelingFinishedTime,
                        TransactionComment = trxComment,
                        FuelItemPumpTotalizerVolume = clientFuelTrxInfo.VolumeTotalizer
                    }
                }
            };

            _client.DefaultRequestHeaders.Clear();
            _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, currentAuthToken.AccessToken);
            _client.DefaultRequestHeaders.Add("DeviceSN", this.Credential.DeviceSN);
            //_client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);

            var response = await _client.PostAsJsonAsync(trxUrl, clientPosTrx).ConfigureAwait(false);
            if (response != null && response.IsSuccessStatusCode)
            {
                var resultStr = await response.Content.ReadAsStringAsync();
                var trxCreationResponse = JsonConvert.DeserializeObject<TrxCreationResponse>(resultStr);
                return trxCreationResponse.Id;
            }

            throw new InvalidOperationException($"CreateTransactionAsync failed(serverUrl: {trxUrl}, trx volume: {clientFuelTrxInfo?.Volume ?? -1 }, deviceSN: {this.Credential?.DeviceSN ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
        }

        public async Task<bool> CommitTransactionAsync(Guid trxId, PosTrxMop posTrxMop, string accountId)
        {
            if (this.currentAuthToken == null)
                await this.RefreshAuthTokenAsync(this.Credential.UserName, this.Credential.Password);
            string targetUrl = "";
            if (string.IsNullOrEmpty(accountId))
                targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/", trxId, "/payment");
            else
                targetUrl = string.Concat(this.Credential.ApiGatewayEntryUrl + "/api/transactions/", trxId, "/payment?accountId=", accountId);
            //Payment detail for receipt
            //string version = "1.0";
            //var icPayDetail = new
            //{
            //    CardNo = clientTrxInfo.CardNo,
            //    PayAmount = clientTrxInfo.PayAmount,
            //    CardHolder = clientTrxInfo.CardHolder,
            //    AccountName = clientTrxInfo.AccountName,
            //    CardBalance = clientTrxInfo.CurrentCardBalance,
            //    Version = version
            //};

            //posTrxMop.Mop = new PosMop
            //{
            //    Id = Guid.Parse("ff64ab36-658d-40a4-94bf-bee7231ac788"),
            //    Name = "IC",
            //    PaymentId = 4,
            //    CreatedDateTime = DateTime.Now,
            //    ChangesetId = Guid.Parse("84f02b52-6950-4c50-a0b1-9827d6459edc"),
            //    TargetBusinessUnitId = Guid.Parse("6c40e7f6-2b2d-40de-8c5c-f5693a05ab0d"),
            //    DisplayName = "IC卡"
            //};

            _client.DefaultRequestHeaders.Clear();
            _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, currentAuthToken.AccessToken);
            _client.DefaultRequestHeaders.Add("DeviceSN", this.Credential.DeviceSN);
            //_client.DefaultRequestHeaders.Add("CurrentBuId", BusinessUnitId);

            var response = await _client.PostAsJsonAsync(targetUrl, posTrxMop).ConfigureAwait(false);
            if (response != null && response.IsSuccessStatusCode)
                return true;
            throw new InvalidOperationException($"CommitTransactionAsync failed(serverUrl: {targetUrl}, deviceSN: {this.Credential?.DeviceSN ?? ""}) due to response with code: {response?.StatusCode.ToString() ?? "-1"}, message: {response?.ReasonPhrase ?? ""}");
        }
    }
}