using InABox.Clients;
using InABox.Core;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;

namespace InABox.IPC
{
    public enum RequestError
    {
        NONE,
        DISCONNECTED,
        UNKNOWN,
        TIMEOUT
    }

    public enum Method
    {
        None,
        Query,
        Save,
        MultiSave,
        Delete,
        MultiDelete,
        QueryMultiple,
        Validate,
        Check2FA,
        Ping,
        Info,
        Push,
        Version,
        ReleaseNotes,
        Installer
    }

    [Serializable]
    public class IPCMessage
    {
        public Guid RequestID;
        public Method Method;
        public string? Type;
        private string? Data;
        private byte[]? BinaryData;

        [NonSerialized]
        public RequestError ErrorCode;

        private IPCMessage(Guid requestID, Method method, string? type, string data, RequestError error = RequestError.NONE)
        {
            RequestID = requestID;
            Method = method;
            Type = type;
            Data = data;
            BinaryData = null;
            ErrorCode = error;
        }
        private IPCMessage(Guid requestID, Method method, string? type, byte[] data, RequestError error = RequestError.NONE)
        {
            RequestID = requestID;
            Method = method;
            Type = type;
            BinaryData = data;
            Data = null;
            ErrorCode = error;
        }

        public IPCMessage Respond<TResponse>(TResponse response) where TResponse : Response
        {
            return CreateRequest(RequestID, Method.None, Type, response);
        }
        private static IPCMessage CreateRequest(Guid requestID, Method method, string? type, object? data)
        {
            if (data is ISerializeBinary binary)
            {
                return new IPCMessage(requestID, method, type, binary.WriteBinary(BinarySerializationSettings.Latest));
            }
            {
                return new IPCMessage(requestID, method, type, Serialization.Serialize(data));
            }
        }

        public TRequest GetRequest<TRequest>()
        {
            if (BinaryData is not null)
            {
                return (TRequest)Serialization.ReadBinary(typeof(TRequest), BinaryData, BinarySerializationSettings.Latest);
            }
            else
            {
                return Serialization.Deserialize<TRequest>(Data);
            }
        }
        public object? GetRequest(Type TRequest)
        {
            if (BinaryData is not null)
            {
                return Serialization.ReadBinary(TRequest, BinaryData, BinarySerializationSettings.Latest);
            }
            else if(Data is not null)
            {
                return Serialization.Deserialize(TRequest, Data);
            }
            else
            {
                return null;
            }
        }

        public TResponse GetResponse<TResponse>() where TResponse : Response, new()
        {
            var start = DateTime.Now;
            var response = GetRequest<TResponse>();
            response ??= new TResponse();
            switch (ErrorCode)
            {
                case RequestError.NONE:
                    break;
                case RequestError.DISCONNECTED:
                    response.Status = StatusCode.Error;
                    response.Messages.Add("Server disconnected");
                    break;
                case RequestError.UNKNOWN:
                    response.Status = StatusCode.Error;
                    response.Messages.Add("Unknown Error");
                    break;
                case RequestError.TIMEOUT:
                    response.Status = StatusCode.Error;
                    response.Messages.Add("Timeout");
                    break;
            }
            return response;
        }

        public static IPCMessage Query<T>(QueryRequest<T> request) where T : Entity, new()
        {
            return CreateRequest(Guid.NewGuid(), Method.Query, typeof(T).Name, request);
        }

        public static IPCMessage Save<T>(SaveRequest<T> request) where T : Entity, new()
        {
            return CreateRequest(Guid.NewGuid(), Method.Save, typeof(T).Name, request);
        }

        public static IPCMessage MultiSave<T>(MultiSaveRequest<T> request) where T : Entity, new()
        {
            return CreateRequest(Guid.NewGuid(), Method.MultiSave, typeof(T).Name, request);
        }

        public static IPCMessage Delete<T>(DeleteRequest<T> request) where T : Entity, new()
        {
            return CreateRequest(Guid.NewGuid(), Method.Delete, typeof(T).Name, request);
        }

        public static IPCMessage MultiDelete<T>(MultiDeleteRequest<T> request) where T : Entity, new()
        {
            return CreateRequest(Guid.NewGuid(), Method.MultiDelete, typeof(T).Name, request);
        }

        public static IPCMessage QueryMultiple(MultiQueryRequest request)
        {
            return CreateRequest(Guid.NewGuid(), Method.QueryMultiple, null, request);
        }

        public static IPCMessage Validate(ValidateRequest request)
        {
            return CreateRequest(Guid.NewGuid(), Method.Validate, null, request);
        }

        public static IPCMessage Check2FA(Check2FARequest request)
        {
            return CreateRequest(Guid.NewGuid(), Method.Check2FA, null, request);
        }

        public static IPCMessage Error(RequestError error)
        {
            return new IPCMessage(Guid.NewGuid(), Method.None, null, "", error);
        }

        public static IPCMessage Ping(PingRequest request)
        {
            return CreateRequest(Guid.NewGuid(), Method.Ping, null, request);
        }
        
        public static IPCMessage Info(InfoRequest request)
        {
            return CreateRequest(Guid.NewGuid(), Method.Info, null, request);
        }

        public static IPCMessage Version(VersionRequest request)
        {
            return CreateRequest(Guid.NewGuid(), Method.Version, null, request);
        }

        public static IPCMessage ReleaseNotes(ReleaseNotesRequest request)
        {
            return CreateRequest(Guid.NewGuid(), Method.ReleaseNotes, null, request);
        }

        public static IPCMessage Installer(InstallerRequest request)
        {
            return CreateRequest(Guid.NewGuid(), Method.Installer, null, request);
        }

        public static IPCMessage Push<TPush>(TPush push) where TPush : BaseObject
            => Push(typeof(TPush), push);
        public static IPCMessage Push(Type TPush, BaseObject push)
        {
            return CreateRequest(Guid.NewGuid(), Method.Push, TPush.EntityName(), push);
        }
    }
}
