using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using InABox.Core;
using InABox.Mail;

namespace InABox.Clients
{

    public class BypassSession : IDisposable
    {
        public BypassSession()
        {
            ClientFactory.SetBypass();
        }

        public void Dispose()
        {
            ClientFactory.UnsetBypass();
        }
    }

    public static class ClientFactory
    {
        public static Dictionary<EmailType, Type> MailerTypes = new Dictionary<EmailType, Type>();

        public static Guid UserGuid { get; private set; }

        public static string UserID { get; private set; }

        public static Guid UserSecurityID { get; private set; }

        public static Guid SessionID { get; private set; }

        public static Guid DatabaseID { get; set; }


        public static Platform Platform { get; private set; }

        public static string? Version { get; private set; }
        
        public static Type? ClientType { get; private set; }
        
        public static object[]? Parameters { get; private set; }

        public static IEnumerable<string>? SupportedTypes { get; private set; }
        
        
        public static string? Recipient2FA { get; private set; }

        public static DateTime PasswordExpiration { get; private set; }

        public static int PINLength { get; } = 4;
        
        public static PushHandlers PushHandlers { get; private set; } = new PushHandlers();

        public delegate void RequestErrorHandler(RequestException e);

        public static event RequestErrorHandler? OnRequestError;

        public static void RaiseRequestError(RequestException e)
        {
            OnRequestError?.Invoke(e);
        }

        public static bool IsSupported<T>() where T : Entity, new()
        {
            return true;
        }

        public static bool IsSupported<T1, T2>() where T1 : Entity, new() where T2 : Entity, new()
        {
            return IsSupported<T1>() && IsSupported<T2>();
        }

        public static bool IsSupported<T1, T2, T3>() where T1 : Entity, new() where T2 : Entity, new() where T3 : Entity, new()
        {
            return IsSupported<T1>() && IsSupported<T2>() && IsSupported<T3>();
        }

        public static bool IsSupported<T1, T2, T3, T4>() where T1 : Entity, new()
            where T2 : Entity, new()
            where T3 : Entity, new()
            where T4 : Entity, new()
        {
            return IsSupported<T1>() && IsSupported<T2>() && IsSupported<T3>() && IsSupported<T4>();
        }

        public static bool IsSupported<T1, T2, T3, T4, T5>() where T1 : Entity, new()
            where T2 : Entity, new()
            where T3 : Entity, new()
            where T4 : Entity, new()
            where T5 : Entity, new()
        {
            return IsSupported<T1>() && IsSupported<T2>() && IsSupported<T3>() && IsSupported<T4>() && IsSupported<T4>();
        }

        public static bool IsSupported(params Type[] types)
        {
            return true;
        }

        public static void SetClientType(Type type, Platform platform, string? version, params object[]? parameters)
        {
            ClientType = type;
            Platform = platform;
            Version = String.IsNullOrEmpty(version) ? Version : version;
            Parameters = parameters == null ? Parameters : parameters;
            SupportedTypes = parameters == null ? SupportedTypes : null;
        }

        public static void ClearClientType()
        {
            ClientType = null;
            Platform = default(Platform);
            Version = "";
            Parameters = null;
        }

        // override the need to provide credentials when configuring the database
        public static void SetBypass()
        {
            UserGuid = CoreUtils.FullGuid;
            UserID = "";

            Request.BeforeRequest = request =>
            {
                if(request is ValidateRequest validate)
                {
                    var ticks = DateTime.Now.ToUniversalTime().Ticks.ToString();
                    validate.UserID = Encryption.Encrypt(ticks, "wCq9rryEJEuHIifYrxRjxg", true);
                    validate.Password = Encryption.Encrypt(ticks, "7mhvLnqMwkCAzN+zNGlyyg", true);
                }
            };

            var ticks = DateTime.Now.ToUniversalTime().Ticks.ToString();
            var result = Validate(
                Encryption.Encrypt(ticks, "wCq9rryEJEuHIifYrxRjxg", true),
                Encryption.Encrypt(ticks, "7mhvLnqMwkCAzN+zNGlyyg", true));
            if (result != ValidationStatus.VALID)
            {
                Logger.Send(LogType.Error, "", "Bypass login failed");
            }

            //Load up the data model, including Custom Properties
            //var props = CreateClient<CustomProperty>().Load();
            //DatabaseSchema.Load(props);
        }
        public static void UnsetBypass()
        {
            Request.BeforeRequest = null;
            UserGuid = Guid.Empty;
        }
        
        public static ValidationStatus Validate(string userid, string password, Guid session = default)
        {
            InvalidateUser();

            var result = Client.Validate(userid, password, session);

            if (result.Status != ValidationStatus.INVALID)
            {
                UserGuid = result.UserGuid;
                UserID = result.UserID;
                UserSecurityID = result.SecurityID;
                Recipient2FA = result.Recipient2FA;
                SessionID = result.SessionID;
                PasswordExpiration = result.PasswordExpiration;

                if(result.Status == ValidationStatus.VALID)
                {
                    OnValidLogin();
                }

                return result.Status;
            }

            return result.Status;
        }

        public static ValidationStatus Validate(string pin, Guid session = default)
        {
            InvalidateUser();

            var result = Client.Validate(pin, session);

            if (result.Status != ValidationStatus.INVALID)
            {
                UserGuid = result.UserGuid;
                UserID = result.UserID;
                UserSecurityID = result.SecurityID;
                Recipient2FA = result.Recipient2FA;
                SessionID = result.SessionID;
                PasswordExpiration = result.PasswordExpiration;

                if (result.Status == ValidationStatus.VALID)
                {
                    OnValidLogin();
                }

                return result.Status;
            }

            return result.Status;
        }

        public static ValidationStatus Validate(Guid session)
        {
            var result = Client.Validate(session);

            if (result.Status != ValidationStatus.INVALID)
            {
                UserGuid = result.UserGuid;
                UserID = result.UserID;
                UserSecurityID = result.SecurityID;
                Recipient2FA = result.Recipient2FA;
                SessionID = result.SessionID;
                PasswordExpiration = result.PasswordExpiration;

                if (result.Status == ValidationStatus.VALID)
                {
                    OnValidLogin();
                }

                return result.Status;
            }

            return result.Status;
        }

        public static bool ValidateUser(string userid, string password)
        {
            InvalidateUser();

            var result = Client.Validate(userid, password);

            if (result.Status == ValidationStatus.VALID)
            {
                UserGuid = result.UserGuid;
                UserID = result.UserID;
                UserSecurityID = result.SecurityID;
                Recipient2FA = result.Recipient2FA;
                SessionID = result.SessionID;
                PasswordExpiration = result.PasswordExpiration;
                
                if (result.Status == ValidationStatus.VALID)
                {
                    OnValidLogin();
                }

                return true;
            }

            return false;
        }

        public static bool ValidatePIN(string pin)
        {
            InvalidateUser();

            var result = Client.Validate(pin);

            if (result.Status == ValidationStatus.VALID)
            {
                UserGuid = result.UserGuid;
                UserID = result.UserID;
                UserSecurityID = result.SecurityID;
                Recipient2FA = result.Recipient2FA;
                SessionID = result.SessionID;
                PasswordExpiration = result.PasswordExpiration;
                
                if (result.Status == ValidationStatus.VALID)
                {
                    OnValidLogin();
                }
                return true;
            }

            return false;
        }

        public static bool Check2FA(string code, Guid? session = null)
        {
            var result = Client.Check2FA(code, session);
            if (result)
            {
                OnValidLogin();
            }
            return result;
        }

        public static void OnValidLogin()
        {
            //Load up the data model, including Custom Properties
            var props = new Client<CustomProperty>().Load();
            DatabaseSchema.Load(props);
        }

        public static void InvalidateUser()
        {
            //CurrentUser = null;
            UserGuid = Guid.Empty;
            UserID = "";
            UserSecurityID = Guid.Empty;
            SessionID = Guid.Empty;
            Security.Reset();
        }

        public static event LogEvent? OnLog;

        public static IClient<TEntity> CreateClient<TEntity>() where TEntity : Entity, new()
        {
            var type = ClientType.MakeGenericType(typeof(TEntity));
            var client = (IClient<TEntity>)Activator.CreateInstance(type, Parameters);
            client.OnLog += OnLog;
            return client;
        }

        public static IClient CreateClient(Type t)
        {
            var type = ClientType.MakeGenericType(t);
            var client = (IClient)Activator.CreateInstance(type, Parameters);
            client.OnLog += OnLog;
            return client;
        }

        public static CoreTable[] MultiQuery(params IQueryDef[] queries)
        {
            var tasks = new Dictionary<object, Task<CoreTable>>();

            for (var i = 0; i < queries.Length; i++)
            {
                var def = queries[i];
                var task = Task.Run(() => { return CreateClient(def.Type).Query(def.Filter, def.Columns, def.SortOrder); });
                tasks[i] = task;
            }

            Task.WaitAll(tasks.Values.ToArray());

            var results = new List<CoreTable>();

            for (var i = 0; i < queries.Length; i++)
                results.Add(tasks[i].Result);

            return results.ToArray();
        }

        public static void MultiQuery(IQueryDef[] queries, Action<CoreTable[]> OnResults)
        {
            Task.Run(() =>
            {
                var tasks = new Dictionary<object, Task<CoreTable>>();

                for (var i = 0; i < queries.Length; i++)
                {
                    var def = queries[i];
                    var task = Task.Run(() => { return CreateClient(def.Type).Query(def.Filter, def.Columns, def.SortOrder); });
                    tasks[i] = task;
                }

                Task.WaitAll(tasks.Values.ToArray());

                var results = new List<CoreTable>();

                for (var i = 0; i < queries.Length; i++)
                    results.Add(tasks[i].Result);

                OnResults.Invoke(results.ToArray());
            });
        }

        public static void RegisterMailer(EmailType type, Type mailer)
        {
            MailerTypes[type] = mailer;
        }

        public static ICoreMailer? CreateMailer()
        {
            var row = new Client<User>().Query(
                new Filter<User>(x => x.ID).IsEqualTo(UserGuid),
                Columns.None<User>().Add(
                    x => x.EmailType,
                    x => x.EmailHost,
                    x => x.EmailPort,
                    x => x.EmailDomain,
                    x => x.EmailUserID,
                    x => x.EmailPassword,
                    x => x.SMTPHost,
                    x => x.SMPTPort,
                    x => x.SMTPUserName,
                    x => x.SMTPPassword)
            ).Rows.FirstOrDefault();

            if (row == null)
                return null;

            var type = row.Get<User, EmailType>(x => x.EmailType);
            if (type == EmailType.None)
                return null;

            var result = (Activator.CreateInstance(MailerTypes[type]) as ICoreMailer)!;
            result.MailboxHost = row.Get<User, string>(x => x.EmailHost);
            result.MailboxPort = row.Get<User, int>(x => x.EmailPort);
            result.MailboxDomain = row.Get<User, string>(x => x.EmailDomain);
            result.MailboxUserName = row.Get<User, string>(x => x.EmailUserID);
            result.MailboxPassword = row.Get<User, string>(x => x.EmailPassword);
            result.SMTPHost = row.Get<User, string>(x => x.SMTPHost);
            result.SMTPPort = row.Get<User, int>(x => x.SMPTPort);
            result.SMTPUserName = row.Get<User, string>(x => x.SMTPUserName);
            result.SMTPPassword = row.Get<User, string>(x => x.SMTPPassword);
            return result;
        }
    }
}