using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Http.Core.Contexts;
using Http.Core.Models;
using static Http.Core.Constants.HttpConstants;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using PCUT.Entities.ApiResponse;
using PCUT.Entities;
using Http.Core.Exceptions;
using System;

namespace Http.Core.Extensions
{
    public static class HttpExtensions
    {
        public static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        };

        public static readonly JsonSerializerSettings IgnoreNullJsonSerializerSettings = new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            NullValueHandling = NullValueHandling.Ignore
        };

        public static readonly JsonSerializer DefaultSerializer = JsonSerializer.Create(JsonSerializerSettings);

        public static string FormatRoute(this string path, params object[] routeParams)
        {
            if (string.IsNullOrEmpty(path))
                return null;
            return string.Format(path, routeParams);
        }

        public static Task<HttpResponseMessage> PostAsJsonAsync(this HttpClient client, string requestUri, object content = null, bool ignoreNull = false)
        {
            var jsonContent = content != null ? JsonConvert.SerializeObject(content, ignoreNull ? IgnoreNullJsonSerializerSettings : JsonSerializerSettings) : null;
            var httpContent = jsonContent != null ? new StringContent(jsonContent, Encoding.UTF8, "application/json") : null;
            return client.PostAsync(requestUri, httpContent);
        }

        public static Task<HttpResponseMessage> PutAsJsonAsync(this HttpClient client, string requestUri, object content = null, bool ignoreNull = false)
        {
            var jsonContent = content != null ? JsonConvert.SerializeObject(content, ignoreNull ? IgnoreNullJsonSerializerSettings : JsonSerializerSettings) : null;
            var httpContent = jsonContent != null ? new StringContent(jsonContent, Encoding.UTF8, "application/json") : null;
            return client.PutAsync(requestUri, httpContent);
        }

        public static Task<HttpResponseMessage> UploadAsync(this HttpClient client, string requestUri, Stream stream, string name, string filename)
        {
            var streamContent = new StreamContent(stream);
            var httpContent = new MultipartFormDataContent
            {
                { streamContent, name, filename }
            };
            return client.PostAsync(requestUri, httpContent);
        }

        public static async Task<bool> DownloadAsync(this HttpClient client, string requestUri, Stream outputStream)
        {
            HttpResponseMessage response = null;
            var isSuccess = true;
            try
            {
                response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead);
                response.EnsureSuccessStatusCode();
                await response.Content.CopyToAsync(outputStream);
            }
            catch (Exception ex) when (!(ex is AppOutdatedException))
            {
                isSuccess = false;
            }
            finally
            {
                response?.Dispose();
            }
            return isSuccess;
        }

        public static async Task<LoginResult> LoginAsync(this HttpClient client, LoginRequest loginRequest, string username)
        {
            var response = await client.PostAsJsonAsync(Auth.Login, loginRequest);
            if (response.IsSuccessStatusCode)
            {
                var userCredential = await response.DeserializeObjectAsync<UserCredentialDto>();
                UserContext.Instance.SetValue(userCredential.AccessToken, userCredential.RefreshToken, username);
                return new LoginResult { Success = true };
            }
            else
            {
                var responseContent = await response.DeserializeObjectAsync<MessageResponse>(ensureSuccess: false);
                return new LoginResult { Success = false, Message = responseContent.ErrorMessage };
            }
        }

        public static async Task<bool> LogOutAsync(this HttpClient client)
        {
            var response = await client.PostAsync(Api.LogOut, null);
            if (response.IsSuccessStatusCode)
            {
                return true;
            }
            return false;
        }

        public static async Task<bool> RefreshTokenAsync(this HttpClient client)
        {
            var response = await client.PostAsJsonAsync(Auth.Refresh, new RefreshTokenRequest());
            if (response.IsSuccessStatusCode)
            {
                var userCredential = await response.DeserializeObjectAsync<UserCredentialDto>();
                UserContext.Instance.SetValue(userCredential.AccessToken, userCredential.RefreshToken, userCredential.Username);
                return true;
            }
            return false;
        }

        public static async Task LoadUserProfileAsync(this HttpClient client)
        {
            var response = await client.GetAsync(Api.Profile);
            if (response.IsSuccessStatusCode)
            {
                var content = await response.DeserializeObjectAsync<DataResponse<UserProfile>>();
                UserContext.Instance.SetProfile(content.Data);
            }
        }

        public static async Task<T> DeserializeObjectAsync<T>(this HttpResponseMessage response, bool ensureSuccess = true)
        {
            if (ensureSuccess)
                response.EnsureSuccessStatusCode();

            if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
                return default;

            var content = response.Content;
            if (content.Headers.ContentLength <= 0 || !content.Headers.ContentType.MediaType.Contains("application/json"))
                return default;

            var contentStream = await response.Content.ReadAsStreamAsync();
            using (var reader = new StreamReader(contentStream))
            using (var jsonReader = new JsonTextReader(reader))
            {
                return DefaultSerializer.Deserialize<T>(jsonReader);
            }
        }

        public static StringBuilder AppendFilter(this StringBuilder builder, string key, string op, string value)
        {
            if (builder.Length > 0)
                builder.Append(',');
            return builder.Append('\"')
                          .Append(key).Append(':')
                          .Append(op).Append(':')
                          .Append(value)
                          .Append('\"');
        }

        public static StringBuilder AppendFilter(this StringBuilder builder, string key, string value)
        {
            return builder.AppendFilter(key, "eq", value);
        }

        public static StringBuilder BuildFilter(this StringBuilder builder)
        {
            return builder.Insert(0, '[').Append(']');
        }

        public class LoginResult
        {
            public bool Success { get; set; }

            [JsonProperty("message")]
            public string Message { get; set; } = string.Empty;
        }
    }
}
