extern alias BouncyCastleA;
using LifePayment.Domain.Shared;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using AeadParametersA = BouncyCastleA.Org.BouncyCastle.Crypto.Parameters.AeadParameters;
using AesEngineA = BouncyCastleA.Org.BouncyCastle.Crypto.Engines.AesEngine;
using GcmBlockCipherA = BouncyCastleA.Org.BouncyCastle.Crypto.Modes.GcmBlockCipher;
using KeyParameterA = BouncyCastleA.Org.BouncyCastle.Crypto.Parameters.KeyParameter;


namespace LifePayment.Domain
{
    public abstract class WxClient
    {
        public IAbpLazyServiceProvider LazyServiceProvider { get; set; }

        protected WxPayOption Options => LazyServiceProvider.LazyGetRequiredService<IOptionsMonitor<WxPayOption>>().CurrentValue;

        protected IHttpClientFactory HttpClientFactory => LazyServiceProvider.LazyGetRequiredService<IHttpClientFactory>();

        protected WxPayRsaHelper WxPayRsaHelper => LazyServiceProvider.LazyGetRequiredService<WxPayRsaHelper>();

        public async Task<TResult> PostAsync<TInput, TResult>(TInput input, string function) where TInput : WxPayPostBaseModel
        {
            var client = HttpClientFactory.CreateClient(LifePaymentConstant.WxPayHttpClientName);
            var body = JsonConvert.SerializeObject(input);
            AddAuthHeader(client, BuildAuth(body, function));
            var data = new StringContent(body, Encoding.UTF8, "application/json");
            var responseMessage = await client.PostAsync($"{Options.Url}{function}", data);
            var str = await responseMessage.Content.ReadAsStringAsync();
            var result = JsonConvert.DeserializeObject<TResult>(str);
            return result;
        }

        public async Task<TResult> NomalPostAsync<TInput, TResult>(TInput input, string function)
        {
            var client = HttpClientFactory.CreateClient(LifePaymentConstant.WxPayHttpClientName);
            var body = JsonConvert.SerializeObject(input);
            AddAuthHeader(client, BuildAuth(body, function));
            var data = new StringContent(body, Encoding.UTF8, "application/json");
            var responseMessage = await client.PostAsync($"{Options.Url}{function}", data);
            var str = await responseMessage.Content.ReadAsStringAsync();
            var result = JsonConvert.DeserializeObject<TResult>(str);
            return result;
        }

        public async Task<TResult> GetAsync<TInput, TResult>(TInput input, string function)
        {
            var client = HttpClientFactory.CreateClient(LifePaymentConstant.WxPayHttpClientName);
            StringBuilder strBuilder = new StringBuilder();
            var type = typeof(TInput);
            strBuilder.Append(function);
            var props = type.GetProperties().ToList();
            props.Where(r => r.GetCustomAttributes(typeof(PathAttribute), true).Any()).ToList().ForEach(r =>
            {
                var name = r.Name;
                var propAttr = (JsonPropertyAttribute)r.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault();
                if (propAttr != null)
                {
                    name = propAttr.PropertyName;
                }

                var value = r.GetValue(input)?.ToString();
                if (!value.IsNullOrEmpty())
                {
                    strBuilder.Append($"/{name}/{value}");
                }
            });
            int i = 0;
            props.Where(r => r.GetCustomAttributes(typeof(QueryAttribute), true).Any()).ToList().ForEach(r =>
            {
                if (i == 0)
                {
                    strBuilder.Append("?");
                }
                else
                {
                    strBuilder.Append("&");
                }

                var value = r.GetValue(input)?.ToString();
                var name = r.Name;
                var propAttr = (JsonPropertyAttribute)r.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault();
                if (propAttr != null)
                {
                    name = propAttr.PropertyName;
                }

                if (!value.IsNullOrEmpty())
                {
                    strBuilder.Append($"{name}={value}");
                }

                i = i + 1;
            });
            AddAuthHeader(client, BuildAuth(string.Empty, strBuilder.ToString(), "GET"));
            var responseMessage = await client.GetAsync(Options.Url + strBuilder.ToString());
            var str = await responseMessage.Content.ReadAsStringAsync();
            var result = JsonConvert.DeserializeObject<TResult>(str);
            var requestBody = JsonConvert.SerializeObject(input);
            return result;
        }

        public async Task<TResult> Certificates<TResult>(string function)
        {
            var client = HttpClientFactory.CreateClient(LifePaymentConstant.WxPayHttpClientName);
            StringBuilder strBuilder = new StringBuilder();
            strBuilder.Append(function);
            AddAuthHeader(client, BuildAuth(string.Empty, strBuilder.ToString(), "GET"));
            var responseMessage = await client.GetAsync(Options.Url + strBuilder.ToString());
            var str = await responseMessage.Content.ReadAsStringAsync();
            var result = JsonConvert.DeserializeObject<TResult>(str);
            return result;
        }

        public async Task<CertificatesReponse> GetCertificates()
        {
            return await Certificates<CertificatesReponse>(LifePaymentConstant.WxPayCertificates);
        }

        public string AesGcmDecrypt(string associatedData, string nonce, string ciphertext)
        {
            GcmBlockCipherA gcmBlockCipher = new GcmBlockCipherA(new AesEngineA());
            AeadParametersA aeadParameters = new AeadParametersA(new KeyParameterA(Encoding.UTF8.GetBytes(Options.APIKey)),
                                                               128,
                                                               Encoding.UTF8.GetBytes(nonce),
                                                               Encoding.UTF8.GetBytes(associatedData));
            gcmBlockCipher.Init(false, aeadParameters);
            byte[] data = Convert.FromBase64String(ciphertext);
            byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(data.Length)];
            int length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, plaintext, 0);
            gcmBlockCipher.DoFinal(plaintext, length);
            return Encoding.UTF8.GetString(plaintext);
        }

        public string GeneratePaySignByKey(string message)
        {
            return WxPayRsaHelper.Sign(message);
        }

        protected void AddAuthHeader(HttpClient client, string authSign)
        {
            client.DefaultRequestHeaders.Add("Authorization", $"WECHATPAY2-SHA256-RSA2048 {authSign}");
        }

        private string BuildAuth(string body, string url, string method = "POST")
        {
            var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
            string nonce = Path.GetRandomFileName();
            string message = $"{method}\n{url}\n{timestamp}\n{nonce}\n{body}\n";
            string signature = WxPayRsaHelper.Sign(message);
            return $"mchid=\"{Options.Mchid}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{Options.SerialNo}\",signature=\"{signature}\"";
        }
    }
}