using System;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using FluentAssertions;
using System.Collections.Concurrent;

namespace raven_integration
{
    public static class Util
    {
        public const string OpEquality = "=";
        public const string OpGreaterThan = ">";
        public const string OpGreaterThanOrEqualTo = ">=";
        public const string OpLessThan = "<";
        public const string OpLessThanOrEqualTo = "<=";
        public const string OpNotEqual = "!=";
        public const string OpNotLike = "!%";
        public const string OpStartsWith = "%-";
        public const string OpEndsWith = "-%";
        public const string OpContains = "-%-";
        public const string OpNotContains = "!-%-";

        private static HttpClient client { get; } = new HttpClient();

        private static string API_BASE_URL = "http://localhost:7575/api/v8.0/";
        //private static string API_BASE_URL = "https://test.helloayanova.com/api/v8.0/";

        public static string TEST_DATA_FOLDER = @"..\..\..\testdata\";

        public static ConcurrentDictionary<string, string> authDict = new ConcurrentDictionary<string, string>();//10,32

        private static AutoId Auto { get; } = new AutoId(0);

        public static string Uniquify(string s)
        {
            // return s + " " + Auto.GetNext().ToString();
            return s + " " + (Auto.GetNext() + ((DateTimeOffset)DateTime.Now).ToUnixTimeMilliseconds()).ToString();
        }

        public async static Task<string> GetTokenAsync(string login, string password = null)
        {
            // Console.WriteLine($"GetTokenAsync:{login}");
            //System.Diagnostics.Trace.WriteLine($"GetTokenAsync:{login}");

            if (password == null)
                password = login;

            if (!authDict.ContainsKey(login))
            {
                dynamic creds = new JObject();
                creds.login = login;
                creds.password = password;

                ApiResponse a = await Util.PostAsync("Auth", null, creds.ToString());
                //Put this in when having concurrency issue during auth and old style dl token creation during login                
                ValidateDataReturnResponseOk(a);

                authDict[login] = a.ObjectResponse["data"]["token"].Value<string>();
            }
            return authDict[login];
        }



        static bool bInitialized = false;
        private static void init()
        {
            if (bInitialized) return;
            if (!System.IO.Directory.Exists(TEST_DATA_FOLDER))
                throw new ArgumentOutOfRangeException($"Test data folder {TEST_DATA_FOLDER} not found, current folder is {System.AppDomain.CurrentDomain.BaseDirectory}");

            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            bInitialized = true;
        }

        public static string CleanApiRoute(string route)
        {
            route = route.TrimStart('/');
            return API_BASE_URL + route;
        }

        public async static Task<ApiResponse> GetAsync(string route, string authToken = null, string bodyJsonData = null)
        {
            init();

            var requestMessage = new HttpRequestMessage(HttpMethod.Get, CleanApiRoute(route));
            if (!string.IsNullOrWhiteSpace(authToken))
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

            if (!string.IsNullOrWhiteSpace(bodyJsonData))
                requestMessage.Content = new StringContent(bodyJsonData, System.Text.Encoding.UTF8, "application/json");

            HttpResponseMessage response = await client.SendAsync(requestMessage);
            var responseAsString = await response.Content.ReadAsStringAsync();

            return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
        }


        public async static Task<ApiTextResponse> GetTextResultAsync(string route, string authToken = null, string bodyJsonData = null)
        {
            init();

            var requestMessage = new HttpRequestMessage(HttpMethod.Get, CleanApiRoute(route));
            if (!string.IsNullOrWhiteSpace(authToken))
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

            if (!string.IsNullOrWhiteSpace(bodyJsonData))
                requestMessage.Content = new StringContent(bodyJsonData, System.Text.Encoding.UTF8, "application/json");

            HttpResponseMessage response = await client.SendAsync(requestMessage);
            var responseAsString = await response.Content.ReadAsStringAsync();

            return new ApiTextResponse() { HttpResponse = response, TextResponse = responseAsString };
        }

        public static async Task<HttpResponseMessage> DownloadFileAsync(string route, string authToken = null)
        {

            init();
            var requestMessage = new HttpRequestMessage(HttpMethod.Get, CleanApiRoute(route));
            if (!string.IsNullOrWhiteSpace(authToken))
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

            HttpResponseMessage response = await client.SendAsync(requestMessage);
            return response;
            // if (response.IsSuccessStatusCode)
            // {
            //     return await response.Content.ReadAsByteArrayAsync();
            // }

            // return null;
        }


        public async static Task<ApiResponse> PostAsync(string route, string authToken = null, string postJson = null)
        {
            init();

            var requestMessage = new HttpRequestMessage(HttpMethod.Post, CleanApiRoute(route));
            if (!string.IsNullOrWhiteSpace(authToken))
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

            if (!string.IsNullOrWhiteSpace(postJson))
                requestMessage.Content = new StringContent(postJson, System.Text.Encoding.UTF8, "application/json");

            HttpResponseMessage response = await client.SendAsync(requestMessage);
            var responseAsString = await response.Content.ReadAsStringAsync();

            return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
        }

        public async static Task<ApiResponse> PostFormDataAsync(string route, MultipartFormDataContent formContent, string authToken = null)
        {
            init();

            var requestMessage = new HttpRequestMessage(HttpMethod.Post, CleanApiRoute(route));
            if (!string.IsNullOrWhiteSpace(authToken))
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

            requestMessage.Content = formContent;

            HttpResponseMessage response = await client.SendAsync(requestMessage);
            var responseAsString = await response.Content.ReadAsStringAsync();

            return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
        }


        public async static Task<ApiResponse> PutAsync(string route, string authToken = null, string putJson = null)
        {
            init();

            var requestMessage = new HttpRequestMessage(HttpMethod.Put, CleanApiRoute(route));
            if (!string.IsNullOrWhiteSpace(authToken))
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

            if (!string.IsNullOrWhiteSpace(putJson))
                requestMessage.Content = new StringContent(putJson, System.Text.Encoding.UTF8, "application/json");

            HttpResponseMessage response = await client.SendAsync(requestMessage);
            var responseAsString = await response.Content.ReadAsStringAsync();

            return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
        }


        public async static Task<ApiResponse> PatchAsync(string route, string authToken = null, string patchJson = null)
        {
            init();

            var method = new HttpMethod("PATCH");

            var requestMessage = new HttpRequestMessage(method, CleanApiRoute(route));
            if (!string.IsNullOrWhiteSpace(authToken))
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

            if (!string.IsNullOrWhiteSpace(patchJson))
                requestMessage.Content = new StringContent(patchJson, System.Text.Encoding.UTF8, "application/json-patch+json");

            HttpResponseMessage response = await client.SendAsync(requestMessage);
            var responseAsString = await response.Content.ReadAsStringAsync();

            return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
        }


        public async static Task<ApiResponse> DeleteAsync(string route, string authToken = null)
        {
            init();

            var requestMessage = new HttpRequestMessage(HttpMethod.Delete, CleanApiRoute(route));
            if (!string.IsNullOrWhiteSpace(authToken))
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

            HttpResponseMessage response = await client.SendAsync(requestMessage);
            var responseAsString = await response.Content.ReadAsStringAsync();

            return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
        }



        /// <summary>
        /// 
        /// </summary>
        /// <param name="jsonString"></param>
        /// <returns></returns>
        private static JObject Parse(string jsonString)
        {
            if (string.IsNullOrWhiteSpace(jsonString))
            {
                return null;
            }
            return JObject.Parse(jsonString);
        }

        //https://www.newtonsoft.com/json/help/html/FromObject.htm
        private static string ObjectToJsonString(object o)
        {
            JObject j = (JObject)JToken.FromObject(o);
            return j.ToString();
        }

        public static void ValidateDataReturnResponseOk(ApiResponse a)
        {
            var ErrorMessage = string.Empty;
            var ERR = a.ObjectResponse["error"];
            if (ERR != null)
            {
                var ecode = ERR["code"];
                if (ecode != null)
                    ErrorMessage += $"CODE: {ecode} ";

                var emsg = ERR["message"];
                if (emsg != null)
                    ErrorMessage += $"MESSAGE: {emsg} ";

                var etarget = ERR["target"];
                if (etarget != null)
                    ErrorMessage += $"TARGET: {etarget} ";
            }

            a.ObjectResponse["error"].Should().BeNull("because there should not be an error on an api call, error was: {0}", ErrorMessage);
            a.ObjectResponse["data"].Should().NotBeNull("A result should be returned");
        }

        public static void ValidateNoErrorInResponse(ApiResponse a)
        {
            a.ObjectResponse["error"].Should().BeNull("There should not be an error on an api call");
        }

        public static void ValidateHTTPStatusCode(ApiResponse a, int DesiredStatusCode)
        {
            ((int)a.HttpResponse.StatusCode).Should().Be(DesiredStatusCode);
        }

        public static void ValidateHTTPStatusCode(ApiTextResponse t, int DesiredStatusCode)
        {
            ((int)t.HttpResponse.StatusCode).Should().Be(DesiredStatusCode);
        }

        /// <summary>
        /// validate a not found response
        /// </summary>
        /// <param name="a"></param>
        public static void ValidateResponseNotFound(ApiResponse a)
        {
            ((int)a.HttpResponse.StatusCode).Should().Be(404);
            a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call");
            a.ObjectResponse["error"]["code"].Value<int>().Should().Be(2010);
        }


        /// <summary>
        /// validate a concurrency error
        /// </summary>
        /// <param name="a"></param>
        public static void ValidateConcurrencyError(ApiResponse a)
        {
            ((int)a.HttpResponse.StatusCode).Should().Be(409);
            a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call");
            a.ObjectResponse["error"]["code"].Value<int>().Should().Be(2005);
        }


        /// <summary>
        /// validate that the call violates referential integrity
        /// </summary>
        /// <param name="a"></param>
        public static void ValidateViolatesReferentialIntegrityError(ApiResponse a)
        {
            ((int)a.HttpResponse.StatusCode).Should().Be(400);
            a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call");
            a.ObjectResponse["error"]["code"].Value<int>().Should().Be(2200);
        }



        /// <summary>
        /// validate a bad ModelState response
        /// </summary>
        /// <param name="a"></param>
        public static void ValidateBadModelStateResponse(ApiResponse a, string CheckFirstTargetExists = null)
        {
            ((int)a.HttpResponse.StatusCode).Should().Be(400);
            a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call");
            a.ObjectResponse["error"]["code"].Value<int>().Should().Be(2200);
            a.ObjectResponse["error"]["details"].Should().NotBeNull("There should be error details on the api call");
            if (!string.IsNullOrWhiteSpace(CheckFirstTargetExists))
            {
                a.ObjectResponse["error"]["details"][0]["target"].Value<string>().Should().Be(CheckFirstTargetExists);
            }
        }




        // public enum ValidationErrorType
        // {
        //     RequiredPropertyEmpty = 1,
        //     LengthExceeded = 2,
        //     NotUnique = 3,
        //     StartDateMustComeBeforeEndDate = 4,
        //     InvalidValue = 5

        // }

        /// <summary>
        ///  assert contains validation target and error code
        /// </summary>
        /// <param name="a"></param>
        /// <param name="target"></param>
        /// <param name="error"></param>
        public static void ShouldContainValidationError(ApiResponse a, string target, string error, string message = null)
        {
            a.ObjectResponse["error"]["details"].Should().NotBeNull("There should be Details on the api call");
            if (message != null)
            {
                a.ObjectResponse["error"]["details"].Should().Contain(
                    m => m["target"].Value<string>() == target &&
                    m["error"].Value<string>() == error &&
                    m["message"].Value<string>() == message);
            }
            else
            {
                a.ObjectResponse["error"]["details"].Should().Contain(m => m["target"].Value<string>() == target && m["error"].Value<string>() == error);
            }
        }


        /// <summary>
        /// validate server exception response
        /// </summary>
        /// <param name="a"></param>
        public static void ValidateServerExceptionResponse(ApiResponse a)
        {
            ((int)a.HttpResponse.StatusCode).Should().Be(500);
            a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call");
            a.ObjectResponse["error"]["code"].Value<int>().Should().Be(2002);
        }


        /// <summary>
        /// Validate an expected api error code and http code response
        /// </summary>
        /// <param name="a"></param>
        /// <param name="apiErrorCode"></param>
        /// <param name="httpStatusCode"></param>
        public static void ValidateErrorCodeResponse(ApiResponse a, int apiErrorCode, int httpStatusCode)
        {
            ((int)a.HttpResponse.StatusCode).Should().Be(httpStatusCode);
            a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call");
            a.ObjectResponse["error"]["code"].Value<int>().Should().Be(apiErrorCode);
        }




    }//eoc
}//eons
