using Fuel.Payment.Core.Models;
using Fuel.Payment.Core.Models.UnionPay;
using Fuel.Payment.Service.Factory;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace Fuel.Payment.Service.UnionPayProcessor
{
    public class MiniUnionPayProcessor : AsyncPaymentProcessor
    {
        private readonly NLog.Logger logger = NLog.LogManager.GetLogger("Main");
        private readonly AutoResetEvent autoEvent = new AutoResetEvent(false);

        public override async Task<GenericProcessResponse> Initialize(ElectronicOrderModel order)
        {
            return new GenericProcessResponse()
            {
                electronicOrderModel = order
            };
        }
        public override async Task<GenericProcessResponse> UnifiedOrder(ElectronicOrderModel order)
        {
            order.TradeStatus = TradeStatus.PAYERROR;
            var response = new Dictionary<string, string>();
            try
            {
                logger.Debug($"UnionPay UnifiedOrder is processing, BillNumber {order.BillNumber}, NetAmount {order.NetAmount}");
                var config = order.Config as UnionPayConfig;
                var paramDic = new Dictionary<string, string>();
                paramDic.Add("requestTimestamp", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                paramDic.Add("merOrderId", order.BillNumber); //{来源编号(4位)}{时间(yyyyMMddmmHHssSSS)(17位)}{7位随机数}
                paramDic.Add("mid", config.mid); //商户号
                paramDic.Add("tid", "00810001"); //终端号
                paramDic.Add("tradeType", "MINI");
                string body = JsonConvert.SerializeObject(paramDic, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
                logger.Debug($"UnionPay UnifiedOrder body: {body}");
                string authorization = Utils.GetAuthorization(config.appId, config.appId, body);
                logger.Debug($"UnionPay UnifiedOrder authorization: {authorization}");
                string payUrl = config.miniUrl + "/v1/netpay/wx/unified-order";
                var client = new DefaultClient();
                string rspStr = client.Post(payUrl, authorization, body, config.charset);
                logger.Debug($"UnionPay UnifiedOrder response: {rspStr}");
                response = JsonConvert.DeserializeObject<Dictionary<string, string>>(rspStr);
                if (response["errCode"] == "errCode")
                {
                    order.TradeStatus = TradeStatus.SUCCESS;
                }
                else
                {
                    logger.Error($"UnionPay UnifiedOrder failed due to: {response["errInfo"]}");
                }
            }
            catch (Exception ex)
            {
                logger.Error($"UnionPay UnifiedOrder exception: {ex}");
            }
            return new GenericProcessResponse()
            {
                AllInPayResponse = response
            };
        }

        public override async Task<GenericProcessResponse> Process(ElectronicOrderModel order)
        {
            order.TradeStatus = TradeStatus.PAYERROR;
            Dictionary<string, string> payResponse = null;
            try
            {
                var config = order.Config as UnionPayConfig;
                logger.Info($"UnionPay is processing, BillNumber {order.BillNumber}, NetAmount {order.NetAmount}, QrCode {order.AuthCode}");
                var paramDic = new Dictionary<string, string>();
                paramDic.Add("merchantCode", config.merchantCode);
                paramDic.Add("terminalCode", "00810001"); //终端号
                paramDic.Add("transactionAmount", ((int)(order.NetAmount * 100)).ToString());
                paramDic.Add("requestTimestamp", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                paramDic.Add("merchantOrderId", order.BillNumber); //{来源编号(4位)}{时间(yyyyMMddmmHHssSSS)(17位)}{7位随机数}
                paramDic.Add("payCode", order.AuthCode);
                paramDic.Add("deviceType", "05");
                string body = JsonConvert.SerializeObject(paramDic, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
                logger.Debug($"UnionPay body: {body}");
                string authorization = Utils.GetAuthorization(config.clientId, config.clientId, body);
                string payUrl = config.scanUrl + "/v4/bc/pay";
                var client = new DefaultClient();
                string rspStr = client.Post(payUrl, authorization, body, config.charset);
                logger.Debug($"UnionPay response: {rspStr}");
                payResponse = JsonConvert.DeserializeObject<Dictionary<string, string>>(rspStr);
                if (payResponse["errCode"] == "00")
                {
                    order.TradeStatus = TradeStatus.SUCCESS;
                    //_ = Task.Delay(6 * 1000).ContinueWith(async _ =>
                    //{
                    //    await Query(order, 1, 1);
                    //});
                    //_ = Task.Delay(6 * 1000).ContinueWith(async _ =>
                    //{
                    //    await Cancel(order);
                    //});
                }
                else
                {
                    logger.Error($"UnionPay failed due to: {payResponse["errInfo"]}");
                    return new GenericProcessResponse()
                    {
                        AllInPayResponse = payResponse
                    };
                }
            }
            catch (Exception ex)
            {
                logger.Error($"UnionPay exception: {ex}");
            }
            return new GenericProcessResponse()
            {
                AllInPayResponse = payResponse
            };
        }

        public override async Task<GenericProcessResponse> Cancel(ElectronicOrderModel order)
        {
            order.TradeStatus = TradeStatus.PAYERROR;
            Dictionary<string, string> payResponse = null;
            try
            {
                logger.Info($"UnionPay is processing cancel, BillNumber {order.BillNumber}");
                var config = order.Config as UnionPayConfig;
                var paramDic = new Dictionary<string, string>();
                paramDic.Add("merchantCode", config.merchantCode);
                paramDic.Add("terminalCode", "00810001"); //终端号
                paramDic.Add("merchantOrderId", order.BillNumber); //{来源编号(4位)}{时间(yyyyMMddmmHHssSSS)(17位)}{7位随机数}
                string body = JsonConvert.SerializeObject(paramDic, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
                logger.Debug($"UnionPay body: {body}");
                string authorization = Utils.GetAuthorization(config.clientId, config.clientId, body);
                string payUrl = config.scanUrl + "/erp/bc/reverse";
                var client = new DefaultClient();
                string rspStr = client.Post(payUrl, authorization, body, config.charset);
                logger.Debug($"UnionPay cancel response: {rspStr}");
                var response = JsonConvert.DeserializeObject<Dictionary<string, string>>(rspStr);
                if (response["errCode"] == "00")
                {
                    order.TradeStatus = TradeStatus.SUCCESS;
                }
                else
                {
                    logger.Error($"UnionPay cancel failed due to: {response["errInfo"]}");
                    return new GenericProcessResponse()
                    {
                        AllInPayResponse = payResponse
                    };
                }
            }
            catch (Exception ex)
            {
                logger.Error($"UnionPay cancel exception: {ex}");
            }
            return new GenericProcessResponse()
            {
                AllInPayResponse = payResponse
            };
        }

        public override async Task<GenericProcessResponse> Query(ElectronicOrderModel order)
        {
            return await Query(order, 16, 6000);
        }

        public override async Task<GenericProcessResponse> Query(ElectronicOrderModel order, int count, int interval)
        {
            order.TradeStatus = TradeStatus.PAYERROR;
            Dictionary<string, string> payResponse = null;
            try
            {
                logger.Info($"UnionPay is processing query, BillNumber {order.BillNumber}");
                var config = order.Config as UnionPayConfig;
                var paramDic = new Dictionary<string, string>();
                paramDic.Add("merchantCode", config.merchantCode);
                paramDic.Add("terminalCode", "00810001"); //终端号
                paramDic.Add("merchantOrderId", order.BillNumber);
                string body = JsonConvert.SerializeObject(paramDic, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
                logger.Debug($"UnionPay body: {body}");
                Dictionary<string, string> response = null;
                response = CoreQuery(config, body);
                if (response["errCode"] == "00")
                {
                    if (response["orderStatus"] == "TRADE_SUCCESS")
                    {
                        order.TradeStatus = TradeStatus.SUCCESS;
                    }
                    else
                    {
                        logger.Error($"UnionPay query failed due to: {response["queryResDesc"]}");
                        return new GenericProcessResponse()
                        {
                            AllInPayResponse = payResponse
                        };
                    }
                }
                else
                {
                    logger.Error($"UnionPay query failed due to: {response["errInfo"]}");
                    return new GenericProcessResponse()
                    {
                        AllInPayResponse = payResponse
                    };
                }
            }
            catch (Exception ex)
            {
                logger.Error($"UnionPay query exception: {ex}");
            }
            return new GenericProcessResponse()
            {
                AllInPayResponse = payResponse
            };
        }

        private Dictionary<string, string> CoreQuery(UnionPayConfig config, string body)
        {
            string authorization = Utils.GetAuthorization(config.clientId, config.clientId, body);
            string payUrl = config.scanUrl + "/erp/bc/query";
            var client = new DefaultClient();
            string rspStr = client.Post(payUrl, authorization, body, config.charset);
            logger.Debug($"UnionPay query response: {rspStr}");
            var response = JsonConvert.DeserializeObject<Dictionary<string, string>>(rspStr);
            return response;
        }

        public override async Task<GenericProcessResponse> Return(ElectronicOrderModel order)
        {
            order.TradeStatus = TradeStatus.PAYERROR;
            Dictionary<string, string> payResponse = null;
            try
            {
                logger.Info($"UnionPay is processing refund, BillNumber {order.BillNumber}, " +
                    $"NetAmount {order.NetAmount}, OperatorId {order.OperatorId}");
                var config = order.Config as UnionPayConfig;
                var paramDic = new Dictionary<string, string>();
                paramDic.Add("merchantCode", config.merchantCode);
                paramDic.Add("terminalCode", "00810001"); //终端号
                paramDic.Add("merchantOrderId", order.BillNumber); //{来源编号(4位)}{时间(yyyyMMddmmHHssSSS)(17位)}{7位随机数}
                paramDic.Add("transactionAmount", ((int)(order.NetAmount * 100)).ToString());
                string body = JsonConvert.SerializeObject(paramDic, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
                logger.Debug($"UnionPay body: {body}");
                string authorization = Utils.GetAuthorization(config.clientId, config.clientId, body);
                string payUrl = config.scanUrl + "/erp/bc/refund";
                var client = new DefaultClient();
                string rspStr = client.Post(payUrl, authorization, body, config.charset);
                logger.Debug($"UnionPay refund response: {rspStr}");
                var response = JsonConvert.DeserializeObject<Dictionary<string, string>>(rspStr);
                if (response["errCode"] == "00")
                {
                    order.TradeStatus = TradeStatus.SUCCESS;
                }
                else
                {
                    logger.Error($"UnionPay refund failed due to: {response["errInfo"]}");
                    return new GenericProcessResponse()
                    {
                        AllInPayResponse = payResponse
                    };
                }
            }
            catch (Exception ex)
            {
                logger.Error($"UnionPay refund exception: {ex}");
            }
            return new GenericProcessResponse()
            {
                AllInPayResponse = payResponse
            };
        }

        public async Task<Dictionary<string, string>> PlaceUnifiedOrder(ElectronicOrderModel order)
        {
            var config = order.Config as UnionPayConfig;
            var paramDic = BuildBasicParam(config);
            paramDic.Add("merOrderId", order.BillNumber);
            paramDic.Add("mid", config.merchantCode); //商户号
            //paramDic.Add("tid", ""); //终端号
            paramDic.Add("tradeType", "MINI");
            //paramDic.Add("trxamt", Convert.ToInt32(order.NetAmount * 100).ToString());
            //paramDic.Add("body", order.Title);
            //paramDic.Add("remark", "");
            //paramDic.Add("acct", order.Optional["mobilePayId"] as string);
            //paramDic.Add("authcode", order.AuthCode);
            //paramDic.Add("notify_url", config.notifyUrl);
            //paramDic.Add("idno", "");
            //paramDic.Add("truename", "");
            //paramDic.Add("asinfo", "");
            //paramDic.Add("validtime", "1");
            //paramDic.Add("sign", AppUtil.signParam(paramDic, config.appKey));
            //return await DoRequest(paramDic, "/pay", config);
            return await Task.FromResult(paramDic);
        }

        private Dictionary<string, string> BuildBasicParam(UnionPayConfig config)
        {
            var paramDic = new Dictionary<string, string>();
            //paramDic.Add("cusid", config.cusId);
            //paramDic.Add("appid", config.appId);
            //paramDic.Add("version", config.apiVersion);
            paramDic.Add("requestTimestamp", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            return paramDic;
        }

        private bool IsSuccessful(Dictionary<string, string> response)
        {
            if (response != null && response.Count > 0)
            {
                if ((response.ContainsKey("retcode") && response["retcode"] == "SUCCESS") &&
                    (response.ContainsKey("trxstatus") && response["trxstatus"] == "0000"))
                {
                    return true;
                }
            }
            return false;
        }

        public bool NotifyPayResult(string xmlPayResult)
        {
            var payResult = new XmlDocument();
            payResult.LoadXml(xmlPayResult);
            var root = payResult.DocumentElement;
            var status = root.SelectSingleNode("status");
            if (status.InnerText != "0")
            {
                var errorMessage = root.SelectSingleNode("message");
                logger.Error($"NotifyPayResult error {errorMessage.InnerText}");
                return false;
            }
            var result_code = root.SelectSingleNode("result_code");
            if (result_code.InnerText != "0")
            {
                var err_msg = root.SelectSingleNode("err_msg");
                logger.Error($"NotifyPayResult error {err_msg.InnerText}");
                return false;
            }
            ThreadPool.QueueUserWorkItem(new WaitCallback(WorkMethod));
            // Simulate time spent working.
            Thread.Sleep(new Random().Next(100, 2000));
            // Signal that work is finished.
            autoEvent.Set();
            return true;
        }

        public void WorkMethod(object stateInfo)
        {
            autoEvent.Reset();
            logger.Debug($"Work starting.");
            // Wait for work method to signal.
            if (autoEvent.WaitOne(1000, false))
            {
                logger.Debug("Work method signaled.");
            }
            else
            {
                logger.Debug("Timed out waiting for work " + "method to signal.");
            }
        }

    }
}
