using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using InABox.Clients;

namespace InABox.Core
{
    public static class Security
    {
        private static ConcurrentBag<ISecurityDescriptor>? _descriptors;
        private static GlobalSecurityToken[]? _globaltokens;
        private static SecurityToken[]? _grouptokens;
        private static UserSecurityToken[]? _usertokens;

        public static IEnumerable<ISecurityDescriptor> Descriptors
        {
            get
            {
                if (_descriptors == null)
                {
                    _descriptors = new ConcurrentBag<ISecurityDescriptor>();

                    var custom = Task.Run(() =>
                    {
                        var tokens = CoreUtils.Entities.Where(
                            x => !x.IsGenericType && x.HasInterface<ISecurityDescriptor>());
                        foreach (var _class in tokens)
                        {
                            var token = (Activator.CreateInstance(_class) as ISecurityDescriptor)!;
                            _descriptors.Add(token);
                        }
                    });

                    var auto = Task.Run(() =>
                    {
                        var tokens = CoreUtils.Entities.Where( x => !x.IsGenericType && x.IsSubclassOf(typeof(Entity))).ToArray();
                        var view = Task.Run(() =>
                        {
                            foreach (var _class in tokens)
                                CheckAutoToken(_class, typeof(CanView<>));
                        });
                        var edit = Task.Run(() =>
                        {
                            foreach (var _class in tokens.Where(x => x.GetCustomAttribute<AutoEntity>() == null))
                                CheckAutoToken(_class, typeof(CanEdit<>));
                        });
                        var delete = Task.Run(() =>
                        {
                            foreach (var _class in tokens.Where(x => x.GetCustomAttribute<AutoEntity>() == null))
                                CheckAutoToken(_class, typeof(CanDelete<>));
                        });
                        var issues = Task.Run(() =>
                        {
                            foreach (var _class in tokens.Where(x => x.GetInterfaces().Contains(typeof(IIssues))))
                                CheckAutoToken(_class, typeof(CanManageIssues<>));
                        });
                        var exports = Task.Run(() =>
                        {
                            foreach (var _class in tokens.Where(x => x.GetInterfaces().Contains(typeof(IExportable))))
                                CheckAutoToken(_class, typeof(CanExport<>));
                        });
                        var imports = Task.Run(() =>
                        {
                            foreach (var _class in tokens.Where(x => x.GetInterfaces().Contains(typeof(IImportable))))
                                CheckAutoToken(_class, typeof(CanImport<>));
                        });
                        var merges = Task.Run(() =>
                        {
                            foreach (var _class in tokens.Where(x => x.GetInterfaces().Contains(typeof(IMergeable))))
                                CheckAutoToken(_class, typeof(CanMerge<>));
                        });
                        var posts = Task.Run(() =>
                        {
                            foreach (var _class in tokens.Where(x => x.GetInterfaces().Contains(typeof(IPostable))))
                                CheckAutoToken(_class, typeof(CanPost<>));
                        });
                        var configPosts = Task.Run(() =>
                        {
                            foreach (var _class in tokens.Where(x => x.GetInterfaces().Contains(typeof(IPostable))))
                                CheckAutoToken(_class, typeof(CanConfigurePost<>));
                        });
                        Task.WaitAll(view, edit, delete, issues, exports, merges, posts, configPosts);
                    });
                    Task.WaitAll(custom, auto);
                }

                return _descriptors.OrderBy(x => x.Type).ThenBy(x => x.Code);
            }
        }

        public static void Reset()
        {
            _globaltokens = null;
            _grouptokens = null;
            _usertokens = null;
            _descriptors = null;
        }

        public static void CheckTokens()
        {
            _usertokens ??= Client.Query(
                new Filter<UserSecurityToken>(x => x.User.ID).IsEqualTo(ClientFactory.UserGuid),
                Columns.None<UserSecurityToken>().Add(x => x.Descriptor).Add(x => x.Enabled)
            ).ToArray<UserSecurityToken>();
            _grouptokens ??= Client.Query(
                new Filter<SecurityToken>(x => x.Group.ID).IsEqualTo(ClientFactory.UserSecurityID),
                Columns.None<SecurityToken>().Add(x => x.Descriptor).Add(x => x.Enabled)
            ).ToArray<SecurityToken>();
            _globaltokens ??= Client.Query(
                null,
                Columns.None<GlobalSecurityToken>().Add(x => x.Descriptor).Add(x => x.Enabled))
            .ToArray<GlobalSecurityToken>();
        }
        
        private static void CheckAutoToken(Type _class, Type type)
        {
            var basetype = typeof(AutoSecurityDescriptor<,>);
            var actiontype = type.MakeGenericType(_class);
            var descriptortype = basetype.MakeGenericType(_class, actiontype);
            var descriptor = (Activator.CreateInstance(descriptortype) as ISecurityDescriptor)!;
            if (!_descriptors.Any(x => string.Equals(x.Code, descriptor.Code)))
                _descriptors.Add(descriptor);
        }

        public static bool IsAllowed(Type T, Guid userGuid, Guid securityId)
        {
            var descriptor = (Activator.CreateInstance(T) as ISecurityDescriptor)!;
            try
            {
                // If you're not logged in, you can't do jack!
                if (userGuid == Guid.Empty)
                    return false;

                CheckTokens();
                
                // First Check for a matching User Token (override)
                var usertoken = _usertokens.FirstOrDefault(x => x.Descriptor.Equals(descriptor.Code));
                if (usertoken != null)
                    return usertoken.Enabled;

                // If not found, fall back to the Group Token
                var grouptoken = _grouptokens.FirstOrDefault(x => x.Descriptor.Equals(descriptor.Code));
                if (grouptoken != null)
                    return grouptoken.Enabled;

                // Still not found? fall back to the Global Token
                var globaltoken = _globaltokens.FirstOrDefault(x => x.Descriptor.Equals(descriptor.Code));
                if (globaltoken != null)
                    return globaltoken.Enabled;
            }
            catch (Exception e)
            {
                Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
            }

            // Aaand finally, just return the default for the descriptor
            return descriptor.Value;
        }

        public static bool IsAllowed<T>(Guid userGuid, Guid securityId) where T : ISecurityDescriptor, new() 
            => IsAllowed(typeof(T), userGuid, securityId);

        public static bool IsAllowed<T>() where T : ISecurityDescriptor, new()
            => IsAllowed<T>(ClientFactory.UserGuid, ClientFactory.UserSecurityID);

        public static bool IsAllowed(Type T) 
            => IsAllowed(T, ClientFactory.UserGuid, ClientFactory.UserSecurityID);

        public static bool CanView<TEntity>(Guid userGuid, Guid securityId) where TEntity : Entity, new()
        {
            return ClientFactory.IsSupported<TEntity>() 
                   && IsAllowed<AutoSecurityDescriptor<TEntity, CanView<TEntity>>>(userGuid, securityId);
        }

        public static bool CanView(Type TEntity)
        {
            return ClientFactory.IsSupported(TEntity) &&
                IsAllowed(typeof(AutoSecurityDescriptor<,>).MakeGenericType(TEntity, typeof(CanView<>).MakeGenericType(TEntity)));
        }
        public static bool CanView<TEntity>() where TEntity : Entity, new()
        {
            return ClientFactory.IsSupported<TEntity>() && IsAllowed<AutoSecurityDescriptor<TEntity, CanView<TEntity>>>();
        }

        public static bool CanEdit(Type TEntity, Guid userGuid, Guid securityId)
        {
            return ClientFactory.IsSupported(TEntity) &&
                IsAllowed(typeof(AutoSecurityDescriptor<,>).MakeGenericType(TEntity, typeof(CanEdit<>).MakeGenericType(TEntity)), userGuid, securityId);
        }
        public static bool CanEdit<TEntity>(Guid userGuid, Guid securityId) where TEntity : Entity, new()
        {
            return ClientFactory.IsSupported<TEntity>() && IsAllowed<AutoSecurityDescriptor<TEntity, CanEdit<TEntity>>>(userGuid, securityId);
        }

        public static bool CanEdit(Type TEntity)
        {
            return ClientFactory.IsSupported(TEntity) &&
                IsAllowed(typeof(AutoSecurityDescriptor<,>).MakeGenericType(TEntity, typeof(CanEdit<>).MakeGenericType(TEntity)));
        }
        public static bool CanEdit<TEntity>() where TEntity : Entity, new()
        {
            return ClientFactory.IsSupported<TEntity>() && IsAllowed<AutoSecurityDescriptor<TEntity, CanEdit<TEntity>>>();
        }

        public static bool CanImport<TEntity>() where TEntity : Entity, new()
        {
            return ClientFactory.IsSupported<TEntity>() && IsAllowed<AutoSecurityDescriptor<TEntity, CanImport<TEntity>>>();
        }

        public static bool CanExport<TEntity>() where TEntity : Entity, new()
        {
            return ClientFactory.IsSupported<TEntity>() && IsAllowed<AutoSecurityDescriptor<TEntity, CanExport<TEntity>>>();
        }

        public static bool CanMerge<TEntity>() where TEntity : Entity, new()
        {
            return ClientFactory.IsSupported<TEntity>() && IsAllowed<AutoSecurityDescriptor<TEntity, CanMerge<TEntity>>>();
        }

        public static bool CanPost<TEntity>() where TEntity : Entity, new()
        {
            return ClientFactory.IsSupported<TEntity>() && IsAllowed<AutoSecurityDescriptor<TEntity, CanPost<TEntity>>>();
        }

        public static bool CanConfigurePost<TEntity>() where TEntity : Entity, new()
        {
            return ClientFactory.IsSupported<TEntity>() && IsAllowed<AutoSecurityDescriptor<TEntity, CanConfigurePost<TEntity>>>();
        }

        public static bool CanDelete<TEntity>() where TEntity : Entity, new()
        {
            return ClientFactory.IsSupported<TEntity>() && IsAllowed<AutoSecurityDescriptor<TEntity, CanDelete<TEntity>>>();
        }
        
        public static bool CanDelete(Type TEntity)
        {
            return ClientFactory.IsSupported(TEntity) &&
                   IsAllowed(typeof(AutoSecurityDescriptor<,>).MakeGenericType(TEntity, typeof(CanDelete<>).MakeGenericType(TEntity)));
        }

        public static bool CanManageIssues(Type TEntity)
        {
            return ClientFactory.IsSupported(TEntity)
                && IsAllowed(typeof(AutoSecurityDescriptor<,>).MakeGenericType(TEntity, typeof(CanManageIssues<>).MakeGenericType(TEntity)));
        }

        public static bool CanManageIssues<TEntity>() where TEntity : Entity, IIssues, new()
        {
            return ClientFactory.IsSupported<TEntity>() && IsAllowed<AutoSecurityDescriptor<TEntity, CanManageIssues<TEntity>>>();
        }
        
        public static bool CanManageProblems(Type TEntity)
        {
            return ClientFactory.IsSupported(TEntity)
                   && IsAllowed(typeof(AutoSecurityDescriptor<,>).MakeGenericType(TEntity, typeof(CanManageProblems<>).MakeGenericType(TEntity)));
        }

        public static bool CanManageProblems<TEntity>() where TEntity : Entity, IProblems, new()
        {
            return ClientFactory.IsSupported<TEntity>() && IsAllowed<AutoSecurityDescriptor<TEntity, CanManageProblems<TEntity>>>();
        }
    }
}