using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;

namespace AyaNovaQBI
{
    internal class util
    {
        #region API stuff
        public const string TEST_ROUTE = "notify/hello";
        public const string API_BASE_ROUTE = "api/v8/";
        private const int MAX_TRIES = 3;//max times to retry an api call before giving up
        private const int API_RETRY_DELAY = 3000;//pause in ms before retrying api call
        public static int HTTPCLIENT_TIMEOUT_SECONDS = 100;//changed by the setting in ops anyway, just a in-case sensible default here
        public static HttpClient client = null;
        //url once known to be good
        internal static string ApiBaseUrl { get; set; }
        //auth processes url for api and this is the best guess as to the client url to use for notification / help links etc
        internal static string GuessClientUrl { get; set; }

        internal static string JWT { get; set; }



        public static void InitClient()
        {
            if (client != null)
            {
                client.Dispose();
                client = null;
            }
            client = new HttpClient();
            client.Timeout = TimeSpan.FromSeconds(HTTPCLIENT_TIMEOUT_SECONDS);
           
        }

        /// <summary>
        /// Only a return value of "OK" is ok
        /// </summary>
        /// <param name="serverUrl"></param>
        /// <returns></returns>
        public static async Task<string> InitAndConfirmAddressAsync(string serverUrl)
        {
            ApiBaseUrl = serverUrl;
            InitClient();
            try
            {
                // TimeSpan tsDefault = client.Timeout;
                //  client.Timeout = new TimeSpan(0, 0, 20);
                HttpResponseMessage response = await client.GetAsync(serverUrl + TEST_ROUTE);
                //  client.Timeout = tsDefault;
                if (response.IsSuccessStatusCode)
                    return "OK";
                else
                    return "Failed: " + response.StatusCode.ToString();
            }
            catch (Exception ex)
            {
                while (ex.InnerException != null)
                    ex = ex.InnerException;
                return "Failed exception: \r\n" + ex.Message;
            }
        }


        public async static Task<bool> AuthenticateAsync(string login, string password = null)
        {
            InitClient();

            if (password == null)
                password = login;

            dynamic creds = new JObject();
            creds.login = login;
            creds.password = password;
            var requestMessage = new HttpRequestMessage(HttpMethod.Post, ApiBaseUrl + "auth");
            requestMessage.Content = new StringContent(creds.ToString(), System.Text.Encoding.UTF8, "application/json");
            HttpResponseMessage response = null;
            try
            {
                response = await client.SendAsync(requestMessage);
            }
            catch (HttpRequestException ex)
            {
                var Err = ex.Message;
                var InnerErr = "";
                if (ex.InnerException != null)
                    InnerErr = ex.InnerException.Message;
                return false;
               // throw new Exception("POST error, route: " + route + "\r\nError:" + Err + "\r\nInner error:" + InnerErr + "\r\nStack:" + ex.StackTrace + "\r\nPOSTED OBJECT:\r\n" + postJson);
            }

            //ApiResponse a = await PostAsync("auth", creds.ToString());

            if (response.IsSuccessStatusCode)
            {

               

                var a= new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(await response.Content.ReadAsStringAsync()) };
                bool tfa = a.ObjectResponse["data"]["tfa"].Value<bool>();
                if (tfa == true)
                {
                    //get 2fa code and send it in
                    tfa t = new tfa();
                    if(t.ShowDialog()==System.Windows.Forms.DialogResult.Cancel) return false;


                }

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

            return false;
        }




        public async static Task<ApiResponse> GetAsync(string route)
        {
            Exception FirstException = null;

            for (int x = 0; x < MAX_TRIES; x++)
            {
                try
                {
                    return await TryGetAsync(route);
                }
                catch (Exception ex)
                {
                    if (FirstException == null)
                        FirstException = ex;
                }
                await Task.Delay(API_RETRY_DELAY);
            }

            //no luck re-throw the exception
            throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException);
        }

        private async static Task<ApiResponse> TryGetAsync(string route)
        {
            var requestMessage = new HttpRequestMessage(HttpMethod.Get, ApiBaseUrl + route);
           
            if (!string.IsNullOrWhiteSpace(JWT))
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT);

            HttpResponseMessage response = null;
            try
            {
                response = await client.SendAsync(requestMessage);
            }
            catch (HttpRequestException ex)
            {
                var Err = ex.Message;
                var InnerErr = "";
                if (ex.InnerException != null)
                    InnerErr = ex.InnerException.Message;
                throw new Exception("GET error, route: " + route + "\r\nError:" + Err + "\r\nInner error:" + InnerErr + "\r\nStack:" + ex.StackTrace);
            }


            var responseAsString = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                throw new Exception("GET error, code: " + (int)response.StatusCode + ", route: " + route + "\r\n" + responseAsString + "\r\n" + response.ReasonPhrase);
            }
            else
                return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
        }




        public async static Task<ApiResponse> PostAsync(string route, dynamic d)
        {
            Exception FirstException = null;

            for (int x = 0; x < MAX_TRIES; x++)
            {
                try
                {
                    return await TryPostAsync(route, d.ToString(Newtonsoft.Json.Formatting.None));
                }
                catch (Exception ex)
                {
                    if (FirstException == null)
                        FirstException = ex;
                }
                await Task.Delay(API_RETRY_DELAY);
            }

            //no luck re-throw the exception
            throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException);
        }

        public async static Task<ApiResponse> PostAsync(string route, string s = null)
        {
            Exception FirstException = null;

            for (int x = 0; x < MAX_TRIES; x++)
            {
                try
                {
                    return await TryPostAsync(route, s);
                }
                catch (Exception ex)
                {
                    if (FirstException == null)
                        FirstException = ex;
                }
                await Task.Delay(API_RETRY_DELAY);
            }

            //no luck re-throw the exception
            throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException);
        }


        internal async static Task<ApiResponse> TryPostAsync(string route, string postJson = null)
        {

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

            if (!string.IsNullOrWhiteSpace(postJson))
                requestMessage.Content = new StringContent(postJson, System.Text.Encoding.UTF8, "application/json");
            HttpResponseMessage response = null;
            try
            {
                response = await client.SendAsync(requestMessage);
            }
            catch (HttpRequestException ex)
            {
                var Err = ex.Message;
                var InnerErr = "";
                if (ex.InnerException != null)
                    InnerErr = ex.InnerException.Message;
                throw new Exception("POST error, route: " + route + "\r\nError:" + Err + "\r\nInner error:" + InnerErr + "\r\nStack:" + ex.StackTrace + "\r\nPOSTED OBJECT:\r\n" + postJson);
            }
            var responseAsString = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                if (string.IsNullOrWhiteSpace(postJson))
                    postJson = "n/a";
                throw new Exception("POST error, code: " + (int)response.StatusCode + ", route: " + route + "\r\n" + responseAsString + "\r\n" + response.ReasonPhrase + "\r\nPOSTED OBJECT:\r\n" + postJson);
            }
            else
                return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
        }




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


        public static long IdFromResponse(ApiResponse a)
        {
            return a.ObjectResponse["data"]["id"].Value<long>();
        }

        public static uint CTokenFromResponse(ApiResponse a)
        {
            return a.ObjectResponse["data"]["concurrency"].Value<uint>();
        }



        public async static Task<ApiResponse> PutAsync(string route, dynamic d)
        {
            Exception FirstException = null;

            for (int x = 0; x < MAX_TRIES; x++)
            {
                try
                {
                    return await TryPutAsync(route, d.ToString(Newtonsoft.Json.Formatting.None));
                }
                catch (Exception ex)
                {
                    if (FirstException == null)
                        FirstException = ex;
                }
                await Task.Delay(API_RETRY_DELAY);
            }

            //no luck re-throw the exception
            throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException);
        }

        public async static Task<ApiResponse> PutAsync(string route)
        {
            Exception FirstException = null;

            for (int x = 0; x < MAX_TRIES; x++)
            {
                try
                {
                    return await TryPutAsync(route);
                }
                catch (Exception ex)
                {
                    if (FirstException == null)
                        FirstException = ex;
                }
                await Task.Delay(API_RETRY_DELAY);
            }

            //no luck re-throw the exception
            throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException);
        }

        public async static Task<ApiResponse> TryPutAsync(string route, string putJson = null)
        {
            var requestMessage = new HttpRequestMessage(HttpMethod.Put, ApiBaseUrl + route);
           
            if (!string.IsNullOrWhiteSpace(JWT))
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT);

            if (!string.IsNullOrWhiteSpace(putJson))
                requestMessage.Content = new StringContent(putJson, System.Text.Encoding.UTF8, "application/json");
            HttpResponseMessage response = null;
            try
            {
                response = await client.SendAsync(requestMessage);
            }
            catch (HttpRequestException ex)
            {
                var Err = ex.Message;
                var InnerErr = "";
                if (ex.InnerException != null)
                    InnerErr = ex.InnerException.Message;
                throw new Exception("PUT error, route: " + route + "\r\nError:" + Err + "\r\nInner error:" + InnerErr + "\r\nStack:" + ex.StackTrace + "\r\nPOSTED OBJECT:\r\n" + putJson);
            }
            var responseAsString = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                if (string.IsNullOrWhiteSpace(putJson))
                    putJson = "n/a";
                throw new Exception("PUT error, code: " + (int)response.StatusCode + ", route: " + route + "\r\n" + responseAsString + "\r\n" + response.ReasonPhrase + "\r\nPUT OBJECT:\r\n" + putJson);
            }
            else
                return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
        }



        public class ApiResponse
        {
            public HttpResponseMessage HttpResponse { get; set; }
            public JObject ObjectResponse { get; set; }
            public string CompactResponse
            {
                get
                {
                    return ObjectResponse.ToString(Newtonsoft.Json.Formatting.None);
                }
            }
        }


        #endregion

        #region QB STUFF
        public static List<InvoiceableItem> GetInvoiceableItems()
        {
            var random = new Random();
            var l = new List<InvoiceableItem>();
            for (int i = 1; i < random.Next(25, 100); i++)
                l.Add(new InvoiceableItem { Customer = $"Customer {random.Next(1, 5)}", Linked = random.Next(2) == 1, Project = $"project {i}", ServiceDate = DateTime.Now.ToString("g"), ServiceNumber = (40 + i).ToString(), Status = $"Waiting to be invoiced", StatusColor = "FF00FFAA", WorkorderId = 4 });

            return l.OrderBy(x => x.Customer)
                                    .ThenBy(x => x.ServiceNumber)
                                    .ThenBy(x => x.ServiceDate)
                                    .ToList();

        }


        public static async Task<bool> InitializeQBI()
        {
            //COPY most of this code from qbi v7 becuase it has a lot of edge cases in it and it's complex and thorough, but break it into abstracted bits so can be replicated in other accounting add-on's more easily

            //This is pre-pfc block of stuff that doesn't map well from v7 qbi plugin to here so replicate it in spirit here but not much is copyable just the concepts
            //LOGIN to v8 first
            auth d = new auth();
            if (d.ShowDialog() == System.Windows.Forms.DialogResult.Cancel)
                return false;

            //Fetch AyaNova license
            //Check if licensed
            //check if build date is within licensed date (how did I do that automated build date thing?)
            //  copy timestamp.cs and the createtimestamp.exe utility from v7 qbi and create build event to run it here and do same thing
            //check that AyaNova version matches required minimum for this QBI (

            //PFC - Util.QBValidate stuff:
            //Validate QB connection can be made and open connection and start session with QB (see Util.QBValidate in v7
            //once connected collect the country, version we are dealing with (Util.qbValidate)
            //confirm qb is 2008 or newer and bail if not (util.qbvalidate)
            //cache company name and other qb info
            //PFC - PopulateQBListCache()
            //PFC - PopulateAyaListCache()
            //PFC - integration object check (fetch or create if not present) (Util.integrationObjectCheck())
            //PFC - Validate settings, create if necessary (Util.ValidateSettings()) and save
            //PFC - verify integration mapped objects still exist at each end (Util.PreFlightCheck() line 199)
            //DONE

            return true;


        }
        #endregion


    }
}
