using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using InABox.Core;
using RestSharp;

//using InABox.Logging;

namespace InABox.Clients
{
    public class CompressedResponse
    {
        public CompressedResponse()
        {
            Headers = new Dictionary<string, string>();
            Options = new Dictionary<string, string>();
        }

        public string ContentType { get; set; }
        public Dictionary<string, string> Headers { get; set; }
        public int Status { get; set; }
        public string StatusCode { get; set; }
        public string Response { get; set; }
        public Dictionary<string, string> Options { get; set; }
    }

    public class JsonClient<TEntity> : RemoteClient<TEntity> where TEntity : Entity, new()
    {
        private readonly bool _compression = true;

        public JsonClient(string url, int port, bool useSimpleEncryption, bool compression) : base(url, port, useSimpleEncryption)
        {
            _compression = compression;
        }

        public JsonClient(string url, int port, bool useSimpleEncryption) : this(url, port, useSimpleEncryption, true)
        {
        }

        public JsonClient(string url, int port) : this(url, port, false, true)
        {
        }


        //private static void CheckSecuritySettings()
        //{
        //    Boolean platformSupportsTls12 = false;
        //    foreach (SecurityProtocolType protocol in Enum.GetValues(typeof(SecurityProtocolType)))
        //        platformSupportsTls12 = platformSupportsTls12 || (protocol.GetHashCode() == 3072);

        //    if (!ServicePointManager.SecurityProtocol.HasFlag((SecurityProtocolType)3072) && platformSupportsTls12)
        //            ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072;

        //    if (ServicePointManager.SecurityProtocol.HasFlag(SecurityProtocolType.Ssl3))
        //        System.Net.ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3;
        //}

        protected override TResponse SendRequest<TRequest, TResponse>(TRequest request, string Action, bool includeEntity = true)
        {
            //DateTime now = DateTime.Now;
            //Log("  * {0}{1}() Starting..", Action, typeof(TEntity).Name);
            //Stopwatch sw = new Stopwatch();
            //sw.Start();

            //CheckSecuritySettings();

            var result = default(TResponse);
            long length = 0;

            if (string.IsNullOrEmpty(URL))
            {
                result = (TResponse)Activator.CreateInstance(typeof(TResponse));
                result.Status = StatusCode.BadServer;
                result.Messages.Add("Server URL not set!");
                return result;
            }

            //sw.Restart();
            var json = Serialization.Serialize(request);
            //var deserialized = Serialization.Deserialize<TRequest>(json);
            //Log("  * {0}{1}() Serializing data took {2}ms ({3} bytes)", Action, typeof(TEntity).Name, sw.ElapsedMilliseconds, json.Length);
            //sw.Restart();

            var uri = new Uri(string.Format("{0}:{1}", URL, Port));
            var cli = new RestClient(uri);
            var cmd = string.Format(
                "{0}{1}?format=json{2}", 
                Action, 
                includeEntity ? typeof(TEntity).Name : "", 
                ""
            );
            var req = new RestRequest(cmd, Method.POST)
            {
                Timeout = Timeout.Milliseconds
            };
            //Log("  * {0}{1}() Creating Uri, Client and RestRequest took {2}ms", Action, typeof(TEntity).Name, sw.ElapsedMilliseconds);
            //sw.Restart();

            req.AdvancedResponseWriter = (stream, response) =>
            {
                //Log("  * {0}{1}() Response from Server took {2}ms ({3} bytes)", Action, typeof(TEntity).Name, sw.ElapsedMilliseconds, response.ContentLength);
                //length = response.ContentLength;
                //sw.Restart();
                //var str = new StreamReader(stream);//.ReadToEnd();
                try
                {
                    result = Serialization.Deserialize<TResponse>(stream, true);
                }
                catch (Exception e)
                {
                    Logger.Send(LogType.Information, "", $"Error deserializing response: {e.Message}");
                }
                //Log("  * {0}{1}() Deserializing Stream took {2}ms ({3} bytes)", Action, typeof(TEntity).Name, sw.ElapsedMilliseconds, response.ContentLength);
            };

            //result = new TResponse();
            req.AddOrUpdateParameter("application/json; charset=utf-8", json, ParameterType.RequestBody);
            req.RequestFormat = DataFormat.Json;
            try
            {
                //sw.Restart();
                var res = cli.Execute(req);
                //Log("  * {0}{1}() returns {2} bytes in {3}ms", Action, typeof(TEntity).Name, res.ContentLength, sw.ElapsedMilliseconds);
                if (result == null)
                {
                    if (res.ErrorException == null)
                    {
                        if (res.StatusCode != HttpStatusCode.OK)
                            throw new Exception(String.Format("HTTP Request returns {0} {1}" + (int)res.StatusCode, CoreUtils.SplitCamelCase(res.StatusCode.ToString())));
                        
                        try
                        {
                            var data = "";

                            if (_compression)
                            {
                                //sw.Restart();
                                var comp = Serialization.Deserialize<CompressedResponse>(res.Content, true);
                                var bytes = Convert.FromBase64String(comp.Response);
                                var ms = new MemoryStream(bytes);
                                using (var resultDeCompressedStream = new MemoryStream())
                                {
                                    using (var decompressionStream = new DeflateStream(ms, CompressionMode.Decompress))
                                    {
                                        decompressionStream.CopyTo(resultDeCompressedStream);

                                        data = Encoding.UTF8.GetString(resultDeCompressedStream.ToArray(), 0,
                                            Convert.ToInt32(resultDeCompressedStream.Length));
                                    }
                                }
                                //Log("  * {0}{1}() Decompressing {2} -> {3} ({4:F2}%) bytes took {5}ms", Action, typeof(TEntity).Name, res.ContentLength, data.Length, ((double)data.Length *100.0F / (double)res.ContentLength), sw.ElapsedMilliseconds);
                            }
                            else
                            {
                                data = res.Content;
                            }

                            //sw.Restart();
                            result = Serialization.Deserialize<TResponse>(data, true);
                            //Log("  * {0}{1}() Deserializing data took {2}ms", Action, typeof(TEntity).Name, sw.ElapsedMilliseconds);
                            //sw.Stop();                      
                        }
                        catch (Exception eDeserialize)
                        {
                            throw new Exception(string.Format("Unable to deserialize response!\n\n{0}\n\n{1}", eDeserialize.Message, res.Content));
                        }
                    }
                    else
                    {
                        // Connectivity
                        result = new TResponse();
                        result.Status = StatusCode.BadServer;
                        result.Messages.Add(res.ErrorMessage);
                    }
                }
            }
            catch (Exception err)
            {
                result = new TResponse();
                result.Status = StatusCode.BadServer;
                result.Messages.Add(err.Message);
                if (err.InnerException != null)
                    result.Messages.Add("- " + err.InnerException.Message);
            }

            req = null;
            cli = null;
            //double elapsed = (DateTime.Now - now).TotalMilliseconds;
            //Log("  * {0}{1}() completed in {2:F0}ms", Action, typeof(TEntity).Name, elapsed);
            return result;
        }

        // SupportedTypes() function fallback for if the server is running on ServiceStack
        private void SupportedTypesOld(RestClient cli, List<string> result)
        {
            var req = new RestRequest("/operations/metadata?format=csv", Method.GET) { Timeout = 20000 };

            try
            {
                var res = cli.Execute(req);
                if (res.ErrorException == null)
                {
                    var lines = res.Content.Split('\n').Skip(1);
                    foreach (var line in lines)
                    {
                        var fields = line.Split(',');
                        var svc = fields[2];
                        if (!result.Contains(svc)) result.Add(svc);
                        //if (svc.Equals("Comal_Classes_Login"))
                        //    result.Add("InABox_Core_Login");
                    }
                }
            }
            catch (Exception e)
            {
            }

            req = null;
            cli = null;
        }

        // This code is duplicated in InABox.Clients.Remote.MsgPack
        // This should stay as-is, and work to remove RestSharp from MsgPack Client
        public override IEnumerable<string> SupportedTypes()
        {
            var result = new List<string>();
            var uri = new Uri(string.Format("{0}:{1}", URL, Port));
            var cli = new RestClient(uri);
            var req = new RestRequest("/classes", Method.GET) { Timeout = 20000 };

            try
            {
                var res = cli.Execute(req);
                if (res.StatusCode != HttpStatusCode.OK)
                {
                    SupportedTypesOld(cli, result);
                }
                else if (res.ErrorException == null)
                {
                    var list = res.Content.Trim('[', ']').Split(',');
                    foreach (var operation in list)
                    {
                        var trimmed = operation.Trim('"');
                        if (!result.Contains(trimmed)) result.Add(trimmed);
                        //if (svc.Equals("Comal_Classes_Login"))
                        //    result.Add("InABox_Core_Login");
                    }
                }
            }
            catch (Exception e)
            {
            }

            req = null;
            cli = null;
            return result.ToArray();
        }

        public override DatabaseInfo Info()
        {
            var uri = new Uri(string.Format("{0}:{1}", URL, Port));
            var cli = new RestClient(uri);
            var req = new RestRequest("/info", Method.GET) { Timeout = 20000 };

            try
            {
                var res = cli.Execute(req);
                if (res.StatusCode != HttpStatusCode.OK)
                {
                    return new DatabaseInfo();
                }
                else if (res.ErrorException == null)
                {
                    var info = Core.Serialization.Deserialize<InfoResponse>(res.Content);
                    return info.Info;
                }
            }
            catch (Exception e)
            {
                return new DatabaseInfo();
            }
            return new DatabaseInfo();
        }
    }
}