using CloudBuilder.Core.DependencyInjection.Request;
using CloudBuilder.Core.Policy;
using CloudBuilder.Core.Service;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
/*
注意是否是全局作用域（winform如果没有加入局部作用域，则都是全局作用域）
全局作用域：
using (var scope1 = ServiceProvider.CreateScope())
{
    YesNoPolicyRequest yesNoPolicyRequest = ServiceProvider.GetRequiredKeyedService<YesNoPolicyRequest>(typeof(YesNoPolicyRequest).Name);
    SelectOption[] selectOptions = yesNoPolicyRequest.FindSelectOption(true);
}
 */

namespace CloudBuilder.Core.Request
{
    public class ServiceRequest : IServiceRequest
    {
        private readonly HttpClient request;
        private readonly IConfiguration configuration;
        private readonly IServiceProvider provider;
        private readonly IContext loginContext;
        private JsonSerializerSettings _jsonSettings;

        public ServiceRequest(HttpClient request, IConfiguration configuration, IServiceProvider provider)
        {
            this.request = request;
            this.configuration = configuration;
            this.provider = provider;
            this.loginContext = provider.GetService<IContext>()!;
            this.request.BaseAddress = new Uri(this.configuration.GetValue<string>("CloudBuilder:ServiceRequest:BaseAddress")!);
            this.request.DefaultRequestHeaders.Add("Accept", this.configuration.GetValue<string>("CloudBuilder:ServiceRequest:DefaultRequestHeaders:Accept"));

            this.request.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginContext.GetToken());

            if (this.request.DefaultRequestHeaders.Contains(ClaimPolicy.HEADER_LOCALE))
                this.request.DefaultRequestHeaders.Remove(ClaimPolicy.HEADER_LOCALE);
            this.request.DefaultRequestHeaders.Add(ClaimPolicy.HEADER_LOCALE, loginContext.GetCulture());

            _jsonSettings = new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore,
                ContractResolver = new DefaultContractResolver(),
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                DateFormatString = "yyyy-MM-dd HH:mm:ss"
            };
        }

        public void SetToken(string token)
        {
            loginContext.SetToken(token);
            this.request.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
        }

        public void SetCulture(string culture)
        {
            loginContext.SetCulture(culture);
            if (this.request.DefaultRequestHeaders.Contains(ClaimPolicy.HEADER_LOCALE))
                this.request.DefaultRequestHeaders.Remove(ClaimPolicy.HEADER_LOCALE);
            this.request.DefaultRequestHeaders.Add(ClaimPolicy.HEADER_LOCALE, culture);
        }

        public void SetToken()
        {
            //如果后面工程注入IContext服務对象，那么就会取出GetToken()，如果为空，防止后面工程沒有注入會出錯。
            IContext loginContext = provider.GetService<IContext>()!;

            if (loginContext != null)
            {
                this.request.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginContext.GetToken());
            }
        }

        public async Task<R> GetAsync<R>(string api)
        {
            return await GetWithHeadersAndQueryAsync<R>(api, null!, null!);
        }

        public async Task<R> GetWithQueryAsync<R>(string api, Dictionary<string, string> queryParams)
        {
            return await GetWithHeadersAndQueryAsync<R>(api, null!, queryParams);
        }

        public async Task<R> GetWithHeadersAsync<R>(string api, Dictionary<string, string> headers)
        {
            return await GetWithHeadersAndQueryAsync<R>(api, headers, null!);
        }

        public async Task<R> GetWithHeadersAndQueryAsync<R>(string api, Dictionary<string, string> headers, Dictionary<string, string> queryParams)
        {
            string url = api;
            if (queryParams != null && queryParams.Count > 0)
            {
                List<string> query = new List<string>();

                foreach (var param in queryParams)
                {
                    query.Add(string.Format("{0}={1}", param.Key, param.Value));
                }

                url = api + "?" + QuotedString.ToDelimiteredList(query.ToArray(), '&');
            }

            if (headers != null && headers.Count > 0)
            {
                foreach (var header in headers)
                {
                    if (request.DefaultRequestHeaders.Contains(header.Key)) request.DefaultRequestHeaders.Remove(header.Key);
                    request.DefaultRequestHeaders.Add(header.Key, header.Value);
                }
            }

            this.request.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginContext.GetToken());

            if (request.DefaultRequestHeaders.Contains(ClaimPolicy.HEADER_LOCALE))
                request.DefaultRequestHeaders.Remove(ClaimPolicy.HEADER_LOCALE);
            request.DefaultRequestHeaders.Add(ClaimPolicy.HEADER_LOCALE, loginContext.GetCulture());

            var response = await request.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
            /*
            await request.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false)
            如果在此处加入ConfigureAwait(false)，返回是数组就会出错。
            要将ConfigureAwait(false)这个方法放入到下面语句
            var responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            */
            if (!response.IsSuccessStatusCode)
            {
                var responseContext = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                string errors = string.Empty;
                if (responseContext != null && responseContext.StartsWith("{\"errors\"")) errors = responseContext.Substring(11, responseContext.IndexOf("}") - 11);

                if (response.ReasonPhrase == "Forbidden")
                    throw new Exception(string.Format("{0}", CoreMessagePolicy.GetInstance().ComposeMessage(CoreMessagePolicy.NO_PERMISSION)));

                throw new Exception(string.Format("{0}{1}", response.ReasonPhrase, string.IsNullOrEmpty(errors) ? "" : "\r\n" + errors));
            }

            response.EnsureSuccessStatusCode();

            var responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            CheckException(responseJson);

            return JsonConvert.DeserializeObject<R>(responseJson, _jsonSettings)!;
        }

        public async Task<R> PostAsJsonAsync<T, R>(string api, T data)
        {
            return await PostAsJsonAsync<T, R>(api, null!, data);
        }
        public async Task<R> PostAsJsonAsync<T, R>(string api, Dictionary<string, string> headers, T data)
        {
            if (headers != null && headers.Count > 0)
            {
                foreach (var header in headers)
                {
                    if (request.DefaultRequestHeaders.Contains(header.Key)) request.DefaultRequestHeaders.Remove(header.Key);
                    request.DefaultRequestHeaders.Add(header.Key, header.Value);
                }
            }

            this.request.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginContext.GetToken());

            if (request.DefaultRequestHeaders.Contains(ClaimPolicy.HEADER_LOCALE))
                request.DefaultRequestHeaders.Remove(ClaimPolicy.HEADER_LOCALE);
            request.DefaultRequestHeaders.Add(ClaimPolicy.HEADER_LOCALE, loginContext.GetCulture());

            var options = new JsonSerializerOptions
            {
                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
            };

            var response = await this.request.PostAsJsonAsync(api, data, options);

            if (!response.IsSuccessStatusCode)
            {
                //{"errors":{},"type":"https://tools.ietf.org/html/rfc9110#section-15.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"00-64ad066978f4471cb3c9ce219c213da9-bccca8afd14676b3-00"}
                var responseContext = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                string errors = string.Empty;
                if (responseContext != null && responseContext.StartsWith("{\"errors\"")) errors = responseContext.Substring(11, responseContext.IndexOf("}") - 11);

                if (response.ReasonPhrase == "Forbidden")
                    throw new Exception(string.Format("{0}", CoreMessagePolicy.GetInstance().ComposeMessage(CoreMessagePolicy.NO_PERMISSION)));

                throw new Exception(string.Format("{0}{1}", response.ReasonPhrase, string.IsNullOrEmpty(errors) ? "" : "\r\n" + errors));
            }

            response.EnsureSuccessStatusCode();

            var responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            CheckException(responseJson);

            return JsonConvert.DeserializeObject<R>(responseJson, _jsonSettings)!;
        }

        private void CheckException(string responseJson)
        {
            if (string.IsNullOrEmpty(responseJson) || !responseJson.StartsWith("{\"SourceCode\":\"CloudBuilder.Core.")) return;

            if (responseJson.StartsWith("{\"SourceCode\":\"CloudBuilder.Core.ValidatedException"))
            {
                ValidatedException vex = JsonConvert.DeserializeObject<ValidatedException>(responseJson, _jsonSettings)!;
                throw vex;
            }
            else if (responseJson.StartsWith("{\"SourceCode\":\"CloudBuilder.Core.ServiceErrorException"))
            {
                ServiceErrorException sex = JsonConvert.DeserializeObject<ServiceErrorException>(responseJson, _jsonSettings)!;
                throw sex;
            }
        }

        public async Task<R> PostAsJsonAsync<R>(string api, Dictionary<string, string> headers)
        {
            if (headers != null && headers.Count > 0)
            {
                foreach (var header in headers)
                {
                    if (request.DefaultRequestHeaders.Contains(header.Key)) request.DefaultRequestHeaders.Remove(header.Key);
                    request.DefaultRequestHeaders.Add(header.Key, header.Value);
                }
            }

            this.request.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginContext.GetToken());

            if (request.DefaultRequestHeaders.Contains(ClaimPolicy.HEADER_LOCALE))
                request.DefaultRequestHeaders.Remove(ClaimPolicy.HEADER_LOCALE);
            request.DefaultRequestHeaders.Add(ClaimPolicy.HEADER_LOCALE, loginContext.GetCulture());

            var response = await this.request.PostAsync(api, null!);

            if (!response.IsSuccessStatusCode)
            {
                var responseContext = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                string errors = string.Empty;
                if (responseContext != null && responseContext.StartsWith("{\"errors\"")) errors = responseContext.Substring(11, responseContext.IndexOf("}") - 11);

                if (response.ReasonPhrase == "Forbidden")
                    throw new Exception(string.Format("{0}", CoreMessagePolicy.GetInstance().ComposeMessage(CoreMessagePolicy.NO_PERMISSION)));

                throw new Exception(string.Format("{0}{1}", response.ReasonPhrase, string.IsNullOrEmpty(errors) ? "" : "\r\n" + errors));
            }

            response.EnsureSuccessStatusCode();

            var responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            CheckException(responseJson);

            return JsonConvert.DeserializeObject<R>(responseJson, _jsonSettings)!;
        }

        public async Task<R> PostAsJsonAsync<R>(string api)
        {
            this.request.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginContext.GetToken());

            if (request.DefaultRequestHeaders.Contains(ClaimPolicy.HEADER_LOCALE))
                request.DefaultRequestHeaders.Remove(ClaimPolicy.HEADER_LOCALE);
            request.DefaultRequestHeaders.Add(ClaimPolicy.HEADER_LOCALE, loginContext.GetCulture());

            var response = await this.request.PostAsync(api, new StringContent(string.Empty));

            if (!response.IsSuccessStatusCode)
            {
                var responseContext = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                string errors = string.Empty;
                if (responseContext != null && responseContext.StartsWith("{\"errors\"")) errors = responseContext.Substring(11, responseContext.IndexOf("}") - 11);

                if (response.ReasonPhrase == "Forbidden")
                    throw new Exception(string.Format("{0}", CoreMessagePolicy.GetInstance().ComposeMessage(CoreMessagePolicy.NO_PERMISSION)));

                throw new Exception(string.Format("{0}{1}", response.ReasonPhrase, string.IsNullOrEmpty(errors) ? "" : "\r\n" + errors));
            }

            response.EnsureSuccessStatusCode();

            var responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            CheckException(responseJson);

            return JsonConvert.DeserializeObject<R>(responseJson, _jsonSettings)!;
        }

        public async Task PostAsync(string api)
        {
            this.request.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginContext.GetToken());

            if (request.DefaultRequestHeaders.Contains(ClaimPolicy.HEADER_LOCALE))
                request.DefaultRequestHeaders.Remove(ClaimPolicy.HEADER_LOCALE);
            request.DefaultRequestHeaders.Add(ClaimPolicy.HEADER_LOCALE, loginContext.GetCulture());

            var response = await this.request.PostAsync(api, new StringContent(string.Empty));

            if (!response.IsSuccessStatusCode)
            {
                var responseContext = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                string errors = string.Empty;
                if (responseContext != null && responseContext.StartsWith("{\"errors\"")) errors = responseContext.Substring(11, responseContext.IndexOf("}") - 11);

                if (response.ReasonPhrase == "Forbidden")
                    throw new Exception(string.Format("{0}", CoreMessagePolicy.GetInstance().ComposeMessage(CoreMessagePolicy.NO_PERMISSION)));

                throw new Exception(string.Format("{0}{1}", response.ReasonPhrase, string.IsNullOrEmpty(errors) ? "" : "\r\n" + errors));
            }

            response.EnsureSuccessStatusCode();

            var responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            CheckException(responseJson);
        }

        public async void DownloadFile(string api, string filePath, Action<long> progressCallback = null!, Action<string> completedCallback = null!)
        {
            this.request.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginContext.GetToken());

            if (request.DefaultRequestHeaders.Contains(ClaimPolicy.HEADER_LOCALE))
                request.DefaultRequestHeaders.Remove(ClaimPolicy.HEADER_LOCALE);
            request.DefaultRequestHeaders.Add(ClaimPolicy.HEADER_LOCALE, loginContext.GetCulture());

            var response = await this.request.PostAsync(api, new StringContent(string.Empty));

            if (!response.IsSuccessStatusCode)
            {
                var responseContext = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                string errors = string.Empty;
                if (responseContext != null && responseContext.StartsWith("{\"errors\"")) errors = responseContext.Substring(11, responseContext.IndexOf("}") - 11);

                if (response.ReasonPhrase == "Forbidden")
                    throw new Exception(string.Format("{0}", CoreMessagePolicy.GetInstance().ComposeMessage(CoreMessagePolicy.NO_PERMISSION)));

                throw new Exception(string.Format("{0}{1}", response.ReasonPhrase, string.IsNullOrEmpty(errors) ? "" : "\r\n" + errors));
            }

            response.EnsureSuccessStatusCode();

            long? totalBytes = response.Content.Headers.ContentLength;

            using (Stream contentStream = await response.Content.ReadAsStreamAsync(),
                    fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                byte[] buffer = new byte[8192];// 8KB緩衝區
                long totalRead = 0;
                int read;

                while ((read = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    await fileStream.WriteAsync(buffer, 0, read);
                    totalRead += read;
                    if (progressCallback != null) progressCallback(totalRead);
                }
            }

            if (completedCallback != null) completedCallback(filePath);
        }

        public async Task<R> PostAsync<R>(string api, MultipartFormDataContent data)
        {
            this.request.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginContext.GetToken());

            if (request.DefaultRequestHeaders.Contains(ClaimPolicy.HEADER_LOCALE))
                request.DefaultRequestHeaders.Remove(ClaimPolicy.HEADER_LOCALE);
            request.DefaultRequestHeaders.Add(ClaimPolicy.HEADER_LOCALE, loginContext.GetCulture());

            var response = await this.request.PostAsync(api, data);

            if (!response.IsSuccessStatusCode)
            {
                var responseContext = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                string errors = string.Empty;
                if (responseContext != null && responseContext.StartsWith("{\"errors\"")) errors = responseContext.Substring(11, responseContext.IndexOf("}") - 11);

                if (response.ReasonPhrase == "Forbidden")
                    throw new Exception(string.Format("{0}", CoreMessagePolicy.GetInstance().ComposeMessage(CoreMessagePolicy.NO_PERMISSION)));

                throw new Exception(string.Format("{0}{1}", response.ReasonPhrase, string.IsNullOrEmpty(errors) ? "" : "\r\n" + errors));
            }

            response.EnsureSuccessStatusCode();

            var responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            CheckException(responseJson);

            return JsonConvert.DeserializeObject<R>(responseJson, _jsonSettings)!;
        }

        public void AddHeaders(string name, string? value)
        {
            request.DefaultRequestHeaders.Add(name, value);
        }

        public void SetContext(IContext loginContext)
        {
            this.loginContext.SetCulture(loginContext.GetCulture());
            this.loginContext.SetToken(loginContext.GetToken());
        }

        public HttpClient GetHttpClient() { return request; }
    }
}
