using InABox.Clients;
using InABox.Core;
using InABox.IPC.Shared;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace InABox.Client.IPC
{
    internal static class LocalCache
    {
        public static string Password { get; set; }

    }

    public class PipeIPCClient<TEntity> : BaseClient<TEntity> where TEntity : Entity, new()
    {
        private IPCClient Client;

        public PipeIPCClient(string pipeName)
        {
            Client = IPCClientFactory.GetClient(pipeName);
        }

        private static string[]? _types;
        public override string[] SupportedTypes()
        {
            _types ??= CoreUtils.Entities
                .Where(x => x.GetInterfaces().Contains(typeof(IPersistent)))
                .Select(x => x.EntityName().Replace(".", "_"))
                .ToArray();
            return _types;
        }
        
        public override DatabaseInfo Info()
        {
            try
            {
                var request = new InfoRequest();
                PrepareRequest(request,false);
                var response = Send(PipeRequest.Info(request)).GetResponse<InfoResponse>();
                return response.Info;
            }
            catch (Exception e)
            {
                return new DatabaseInfo();
            }

        }
        
        private void PrepareRequest(Request request, bool doCredentials = true)
        {
            if(request is not ValidateRequest && Client.Disconnected)
            {
                ClientFactory.Validate(ClientFactory.UserID, LocalCache.Password);
            }

            if (doCredentials)
            {
                request.Credentials.Platform = ClientFactory.Platform;
                request.Credentials.Version = ClientFactory.Version;
                request.Credentials.Session = ClientFactory.SessionID;
            }

            Request.BeforeRequest?.Invoke(request);
        }

        private PipeRequest Send(PipeRequest request, int timeout = 300000)
        {
            return Client.Send(request, timeout);
        }

        protected override bool DoCheck2FA(string code, Guid? session)
        {
            var request = new Check2FARequest { Code = code };

            PrepareRequest(request);

            var response = Send(PipeRequest.Check2FA(request)).GetResponse<Check2FAResponse>();
            if (response != null)
            {
                return response.Status switch
                {
                    StatusCode.OK => response.Valid,
                    StatusCode.Unauthenticated => false,
                    _ => throw new IPCException(response.Messages),
                };
            }

            return false;
        }

        protected override bool DoPing()
        {
            try
            {
                var request = new PingRequest();

                PrepareRequest(request);

                var response = Send(PipeRequest.Ping(request), 10_000).GetResponse<PingResponse>();
                if (response != null)
                {
                    return response.Status switch
                    {
                        StatusCode.Error or StatusCode.BadServer or StatusCode.Incomplete => throw new IPCException(response.Messages),
                        _ => true
                    };
                }
            }
            catch (Exception) { }

            return false;
        }
        
        protected override void DoDelete(TEntity entity, string auditnote)
        {
            var request = new DeleteRequest<TEntity> { Item = entity };
            PrepareRequest(request);

            var response = Send(PipeRequest.Delete(request)).GetResponse<DeleteResponse<TEntity>>();
            switch (response.Status)
            {
                case StatusCode.OK:
                    break;
                case StatusCode.Unauthenticated:
                    throw new IPCException("Client not authenticated");
                default:
                    throw new IPCException(response.Messages);
            }
        }

        protected override void DoDelete(IList<TEntity> entities, string auditnote)
        {
            var items = entities.ToArray();
            var request = new MultiDeleteRequest<TEntity> { Items = items, AuditNote = auditnote };
            PrepareRequest(request);

            var response = Send(PipeRequest.MultiDelete(request)).GetResponse<MultiDeleteResponse<TEntity>>();
            switch (response.Status)
            {
                case StatusCode.OK:
                    break;
                case StatusCode.Unauthenticated:
                    throw new IPCException("Client not authenticated");
                default:
                    throw new IPCException(response.Messages);
            }
        }

        protected override TEntity[] DoLoad(Filter<TEntity> filter = null, SortOrder<TEntity> sort = null)
        {
            var request = new QueryRequest<TEntity>
            {
                Filter = filter,
                Sort = sort
            };
            PrepareRequest(request);

            var result = new List<TEntity>();
            var response = Send(PipeRequest.Query(request)).GetResponse<QueryResponse<TEntity>>();
            if (response.Items != null)
                foreach (var row in response.Items.Rows)
                    result.Add(row.ToObject<TEntity>());
            return result.ToArray();
        }

        protected override CoreTable DoQuery(Filter<TEntity> filter, Columns<TEntity> columns, SortOrder<TEntity> sort = null)
        {
            var request = new QueryRequest<TEntity>
            {
                Columns = columns,
                Filter = filter,
                Sort = sort
            };
            PrepareRequest(request);

            var response = Send(PipeRequest.Query(request)).GetResponse<QueryResponse<TEntity>>();

            if (response != null)
            {
                return response.Status switch
                {
                    StatusCode.OK => response.Items,
                    StatusCode.Unauthenticated => throw new IPCException("Client not authenticated", StatusCode.Unauthenticated),
                    _ => throw new IPCException(response.Messages),
                };
            }

            return null;
        }

        protected override Dictionary<string, CoreTable> DoQueryMultiple(Dictionary<string, IQueryDef> queries)
        {
            var request = new MultiQueryRequest
            {
                TableTypes = new(),
                Filters = new(),
                Columns = new(),
                Sorts = new()
            };
            foreach (var item in queries)
            {
                request.TableTypes[item.Key] = item.Value.Type.EntityName();
                request.Filters[item.Key] = Serialization.Serialize(item.Value.Filter);
                request.Columns[item.Key] = Serialization.Serialize(item.Value.Columns);
                request.Sorts[item.Key] = Serialization.Serialize(item.Value.SortOrder);
            }
            PrepareRequest(request);

            var response = Send(PipeRequest.QueryMultiple(request)).GetResponse<MultiQueryResponse>();
            if (response != null)
            {
                return response.Status switch
                {
                    StatusCode.OK => response.Tables,
                    StatusCode.Unauthenticated => throw new IPCException("Client not authenticated"),
                    _ => throw new IPCException(response.Messages),
                };
            }

            return null;
        }

        protected override void DoSave(TEntity entity, string auditnote)
        {
            var request = new SaveRequest<TEntity>
            {
                Item = entity,
                AuditNote = auditnote
            };
            PrepareRequest(request);

            var response = Send(PipeRequest.Save(request)).GetResponse<SaveResponse<TEntity>>();
            switch (response.Status)
            {
                case StatusCode.OK:
                    var props = CoreUtils.PropertyList(typeof(TEntity), x => true, true);
                    entity.SetObserving(false);
                    foreach (var prop in props.Keys)
                    {
                        var value = CoreUtils.GetPropertyValue(response.Item, prop);
                        CoreUtils.SetPropertyValue(entity, prop, value);
                    }

                    entity.CommitChanges();
                    entity.SetObserving(true);
                    break;
                case StatusCode.Unauthenticated:
                    throw new IPCException("Client not authenticated");
                default:
                    throw new IPCException(response.Messages);
            }
        }

        protected override void DoSave(IEnumerable<TEntity> entities, string auditnote)
        {
            var items = entities.ToArray();
            var request = new MultiSaveRequest<TEntity>
            {
                Items = items,
                AuditNote = auditnote
            };
            PrepareRequest(request);

            var response = Send(PipeRequest.MultiSave(request)).GetResponse<MultiSaveResponse<TEntity>>();
            switch (response.Status)
            {
                case StatusCode.OK:
                    var props = CoreUtils.PropertyList(typeof(TEntity), x => true, true);
                    for (var i = 0; i < items.Length; i++)
                    {
                        items[i].SetObserving(false);
                        foreach (var prop in props.Keys)
                        {
                            var value = CoreUtils.GetPropertyValue(response.Items[i], prop);
                            CoreUtils.SetPropertyValue(items[i], prop, value);
                        }

                        //CoreUtils.DeepClone<TEntity>(response.Items[i], items[i]);
                        items[i].CommitChanges();
                        items[i].SetObserving(true);
                    }
                    break;
                case StatusCode.Unauthenticated:
                    throw new IPCException("Client not authenticated");
                default:
                    throw new IPCException(response.Messages);
            }
        }

        protected override ValidationData DoValidate(Guid session)
        {
            return Validate(
                null, null, false, session);
        }

        protected override ValidationData DoValidate(string pin, Guid session)
        {
            return Validate(
                null, pin, true, session);
        }

        protected override ValidationData DoValidate(string userid, string password, Guid session)
        {
            return Validate(
                userid, password, false, session);
        }

        private ValidationData Validate(string? userid, string? password, bool usePin, Guid session = default)
        {
            var ticks = DateTime.Now.ToUniversalTime().Ticks.ToString();
            var request = new ValidateRequest { UsePIN = usePin };

            if (usePin)
            {
                request.UserID = Encryption.Encrypt(ticks, "wCq9rryEJEuHIifYrxRjxg", true);
                request.Password = Encryption.Encrypt(ticks, "7mhvLnqMwkCAzN+zNGlyyg", true);
                request.PIN = password;
            }
            else
            {
                request.UserID = userid;
                request.Password = password;
            }
            request.Credentials.Platform = ClientFactory.Platform;
            request.Credentials.Version = ClientFactory.Version;
            PrepareRequest(request, false);
            if(session != Guid.Empty)
            {
                request.Credentials.Session = session;
            }

            var response = Send(PipeRequest.Validate(request), 10000).GetResponse<ValidateResponse>();
            if (response != null)
                if (response.Status.Equals(StatusCode.OK))
                {
                    LocalCache.Password = password;
                    return new ValidationData(
                        response.ValidationResult,
                        response.UserID,
                        response.UserGuid,
                        response.SecurityID,
                        response.Session,
                        response.Recipient2FA,
                        response.PasswordExpiration

                    );
                }
                else if (response.Status == StatusCode.BadServer)
                {
                    throw new IPCException(response.Messages);
                }

            return new ValidationData(
                ValidationResult.INVALID,
                "",
                Guid.Empty,
                Guid.Empty,
                Guid.Empty,
                null,
                DateTime.MinValue
                );
        }
        
    }
}
