using System.Security.Cryptography.X509Certificates;
using AyCode.Database.DbSets.Users;
using AyCode.Models.Enums;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using TIAM.Core;
//using TIAM.Database.DataLayers.ServiceProviders;
using TIAM.Database.DbContexts.Admins;
using TIAM.Database.DbSets.Emails;
using TIAM.Database.DbSets.Permissions;
using TIAM.Database.DbSets.Products;
using TIAM.Database.DbSets.Transfers;
using TIAM.Database.DbSets.Users;
using TIAM.Entities.Drivers;
using TIAM.Entities.Emails;
using TIAM.Entities.Permissions;
using TIAM.Entities.Products;
using TIAM.Entities.ServiceProviders;
using TIAM.Entities.TransferDestinations;
using TIAM.Entities.Transfers;
using TIAM.Entities.Users;
using TIAM.Models.Dtos.Products;
using TIAM.Models.Dtos.Users;

namespace TIAM.Database.DataLayers.Admins
{
    public class AdminDal : DalBase<AdminDbContext>
    {
        public AdminDal() : base()
        {
        }

        #region Transfer
        public TransferDestination? GetTransferDestinationById(Guid transferDestinationId, bool autoInclude = false) => Session(ctx=>ctx.GetTransferDestinationById(transferDestinationId));
        public string? GetTransferDestinationJsonById(Guid transferDestinationId) => Session(ctx => ctx.GetTransferDestinationById(transferDestinationId)?.ToJson());

        public Transfer? GetTransferById(Guid transferId, bool autoInclude = false) => Session(ctx => ctx.GetTransferById(transferId, autoInclude));
        public string? GetTransferJsonById(Guid transferId, bool autoInclude = false) => Session(ctx => ctx.GetTransferById(transferId, autoInclude)?.ToJson());

        public Task<bool> AddTransferAsync(Transfer transfer) => TransactionAsync(ctx => ctx.AddTransfer(transfer) && ctx.SaveChanges() > 0);
        public Task<bool> UpdateTransferAsync(Transfer transfer) => TransactionAsync(ctx => ctx.UpdateTransfer(transfer) && ctx.SaveChanges() > 0);
        public Task<bool> RemoveTransferAsync(Transfer transfer) => TransactionAsync(ctx => ctx.RemoveTransfer(transfer) && ctx.SaveChanges() > 0);
        public Task<bool> RemoveTransferAsync(Guid transferId) => TransactionAsync(ctx => ctx.RemoveTransfer(transferId) && ctx.SaveChanges() > 0);
        #endregion Transfer

        #region TransferDestination
        public Task<bool> AddTransferDestinationAsync(TransferDestination transferDestination) => TransactionAsync(ctx => ctx.AddTransferDestination(transferDestination) && ctx.SaveChanges() > 0);
        public Task<bool> UpdateTransferDestinationAsync(TransferDestination transferDestination) => TransactionAsync(ctx => ctx.UpdateTransferDestination(transferDestination) && ctx.SaveChanges() > 0);
        public Task<bool> RemoveTransferDestinationAsync(TransferDestination transferDestination) => TransactionAsync(ctx => ctx.RemoveTransferDestination(transferDestination) && ctx.SaveChanges() > 0);
        public Task<bool> RemoveTransferDestinationAsync(Guid transferDestinationId) => TransactionAsync(ctx => ctx.RemoveTransferDestination(transferDestinationId) && ctx.SaveChanges() > 0);

        #endregion TransferDestination

        public User? GetUserById(Guid userId, bool autoInclude = false) => Session(ctx => ctx.GetUserById(userId, autoInclude));
        public User? GetUserByEmail(string email, bool autoInclude = false) => Session(ctx => ctx.GetUserByEmail(email, autoInclude));

        public UserModelDto? GetUserModelDtoById(Guid userId) => Session(ctx => ctx.GetUserModelDtoById(userId));
        public Task<UserModelDto?> GetUserModelDtoByIdAsync(Guid userId) => SessionAsync(ctx => ctx.GetUserModelDtoById(userId));
        public UserModelDto? GetUserModelDtoByEmail(string email) => Session(ctx => ctx.GetUserModelDtoByEmail(email));

        public string? GetUserJsonById(Guid userId) => Session(ctx => ctx.GetUserById(userId)?.ToJson());
        public string GetUsersJson() => Session(ctx => ctx.Users.ToJson());
        
        public Product? GetProductById(Guid contextId, bool includeUsers = true) => Session(ctx => ctx.GetProductById(contextId, includeUsers));

        public string GetProductsJson(bool includeUsers = true) => Session(ctx => ctx.ProductsWithUserRelations(includeUsers).ToJson());
        public List<Product> GetProductsByServiceProviderId(Guid serviceProviderId, bool includeUsers = true) => Session(ctx => ctx.GetProductsByServiceProviderId(serviceProviderId, includeUsers).ToList());
        public string GetProductsJsonByServiceProviderId(Guid serviceProviderId, bool includeUsers = true) => Session(ctx => ctx.GetProductsByServiceProviderId(serviceProviderId, includeUsers).ToJson());
        public Task<bool> AddProduct(Product product) => TransactionAsync(ctx => ctx.AddProduct(product) && ctx.SaveChanges() > 0);

        public Task<bool> UpdateProduct(Product product) => TransactionAsync(ctx => ctx.UpdateProduct(product) && ctx.SaveChanges() > 0);
        public Task<bool> RemoveProduct(Product product) => TransactionAsync(ctx => ctx.RemoveProduct(product) && ctx.SaveChanges() > 0);

        public UserProductMapping? GetUserProductMappingById(Guid userProductMappingId, bool autoInclude = true) => Session(ctx => ctx.GetUserProductMappingById(userProductMappingId, autoInclude));
        public Task<UserProductMapping?> GetUserProductMappingByIdAsync(Guid userProductMappingId, bool autoInclude = true) => SessionAsync(ctx => ctx.GetUserProductMappingById(userProductMappingId, autoInclude));

        public List<PermissionContextMapping> GetPermissionContextsView(Guid subjectId, Guid contextId)
            => Session(x => x.GetPermissionContextsView(subjectId, contextId).ToList());

        public List<PermissionContextMapping> GetPermissionContextsViewBySubjectId(Guid contextId)
            => Session(x => x.GetPermissionContextsViewBySubjectId(contextId).ToList());

        public List<PermissionContextMapping> GetPermissionContextsViewByContextId(Guid contextId)
            => Session(x => x.GetPermissionContextsViewByContextId(contextId).ToList());

        public Task<List<PermissionContextMapping>> GetPermissionContextsViewByContextIdAsync(Guid contextId)
            => SessionAsync(x => x.GetPermissionContextsViewByContextId(contextId).ToList());

        #region UserProductMapping
        public Task<bool> AddUserProductMappingAsync(UserProductMapping userProductMapping) 
            => TransactionAsync(ctx => ctx.AddUserProductMapping(userProductMapping) && ctx.SaveChanges() > 0);

        public async Task<UserProductMapping?> AddUserProductMappingAsync(Guid userProductMappingId, Guid userId, Guid productId, int permissions = 1, UserProductJsonDetailModel? userProductToCars = null)
        {
            UserProductMapping? userProductMapping = null;

            var isSucces = await TransactionAsync(ctx =>
            {
                userProductMapping = ctx.AddUserProductMapping(userProductMappingId, userId, productId, permissions, userProductToCars);
                
                return userProductMapping != null && ctx.SaveChanges() > 0;
            });

            return isSucces ? userProductMapping : null;
        }

        public Task<bool> UpdateUserProductMappingAsync(UserProductMapping userProductMapping) 
            => TransactionAsync(ctx => ctx.UpdateUserProductMapping(userProductMapping) && ctx.SaveChanges() > 0);

        public async Task<UserProductMapping?> UpdateUserProductMappingAsync(Guid userProductMappingId, int permissions = 1, UserProductJsonDetailModel? userProductToCars  = null)
        {
            UserProductMapping? userProductMapping = null;

            var isSucces = await TransactionAsync(ctx =>
            {
                userProductMapping = ctx.UpdateUserProductMapping(userProductMappingId, permissions, userProductToCars);
                
                return userProductMapping != null && ctx.SaveChanges() > 0;
            });

            return isSucces ? userProductMapping : null;
        }

        public Task<bool> RemoveUserProductMappingAsync(Guid userProductMappingId) 
            => TransactionAsync(ctx => ctx.RemoveUserProductMapping(userProductMappingId) && ctx.SaveChanges() > 0);

        public Task<bool> RemoveUserProductMappingAsync(Guid userId, Guid productId) 
            => TransactionAsync(ctx => ctx.RemoveUserProductMapping(userId, productId) && ctx.SaveChanges() > 0);

        #endregion UserProductMapping

        #region EmailMessage
        public Task<EmailMessage?> GetEmailMessageByIdAsync(Guid emailMessageId) => SessionAsync(ctx => ctx.GetEmailMessageById(emailMessageId));
        public Task<List<EmailMessage>> GetEmailMessagesByContextIdAsync(Guid contextId) => SessionAsync(ctx => ctx.GetEmailMessagesByContextId(contextId).ToList());
        public Task<List<EmailMessage>> GetEmailMessagesBySenderIdAsync(Guid senderId) => SessionAsync(ctx => ctx.GetEmailMessagesBySenderId(senderId).ToList());
        public Task<List<EmailMessage>> GetEmailMessagesBySenderEmailAddressAsync(string emailAddress) => SessionAsync(ctx => ctx.GetEmailMessagesBySenderEmailAddress(emailAddress).ToList());
        public Task<List<EmailMessage>> GetEmailMessagesAsync(Guid userId, Guid userProductMappingId) => SessionAsync(ctx => ctx.GetEmailMessages(userId, userProductMappingId).ToList());
        public Task<List<EmailMessage>> GetEmailMessagesAsync(Guid contextId, Guid userId, Guid userProductMappingId) => SessionAsync(ctx => ctx.GetEmailMessages(contextId, userId, userProductMappingId).ToList());


        public Task<bool> AddEmailMessageAsync(EmailMessage emailMessage)
            => TransactionAsync(ctx => ctx.AddEmailMessage(emailMessage) && ctx.SaveChanges() > 0);

        public Task<bool> UpdateEmailMessageAsync(EmailMessage emailMessage) 
            => TransactionAsync(ctx => ctx.UpdateEmailMessage(emailMessage) && ctx.SaveChanges() > 0);

        public Task<bool> RemoveEmailMessageAsync(Guid emailMessageId) 
            => TransactionAsync(ctx => ctx.RemoveEmailMessage(emailMessageId) && ctx.SaveChanges() > 0);

        #endregion EmailMessage

        //15. (IServiceProviderDataService) Create service provider
        public Task<bool> CreateServiceProviderAsync(TiamServiceProvider serviceProvider)
        {
            Context.CreateServiceProvider(serviceProvider);
            return Context.SaveChangesAsync().ContinueWith(x => x.Result > 0);
        }

        public bool CreateProductAsync(Product product)
        {
            Context.CreateProduct(product);
            Console.WriteLine($"Saving product to db {product.Id}, {product.Name}, {product.ServiceProviderId}");
            var _result = Context.SaveChangesAsync();
            return _result.Result > 0;
        }

        public Task<List<TiamServiceProvider>> GetServiceProvidersAsync()
        {
            return Context.ServiceProviders.ToListAsync();
        }

        public virtual Task<TiamServiceProvider?> GetServiceProviderByIdAsync(Guid id)
        {
            Console.WriteLine($"Getting serviceProvider from db {id}");
            return Context.ServiceProviders.SingleOrDefaultAsync(x => x.Id == id);
        }

        //public Task<UserProductMapping> CreateUserProductMappingAsync(UserProductMapping userProductMapping)
        //{
        //    Context.UserProductMappings.Add(userProductMapping);
        //    Console.WriteLine($"Saving userProductMapping to db {userProductMapping.Id}, {userProductMapping.ProductId}, {userProductMapping.UserId}");
        //    return Context.SaveChangesAsync().ContinueWith(x => userProductMapping);
        //}

        #region ServiceProviders

        //14. (IserviceProviderDataService) Update service provider
        public Task<bool> UpdateServiceProviderAsync(TiamServiceProvider serviceProvider)
        {
            var dbServiceProvider = Context.ServiceProviders.FirstOrDefault(u => u.Id == serviceProvider.Id);
            if (dbServiceProvider != null)
            {
                dbServiceProvider = serviceProvider;
                Context.ServiceProviders.Update(dbServiceProvider);
                return Context.SaveChangesAsync().ContinueWith(x => x.Result > 0);
            }
            else
            {
                throw new Exception("ServiceProvider not found");
            }
        }

        //13. (IserviceProviderDataService) delete service provider
        public Task<bool> DeleteServiceProviderAsync(Guid id)
        {
            using (var transaction = Context.Database.BeginTransaction())
            {
                var dbServiceProvider = Context.ServiceProviders.FirstOrDefault(u => u.Id == id);
                if (dbServiceProvider != null)
                {
                    //get products for this provider
                    var products = Context.Products.Where(x => x.ServiceProviderId == id).ToList();

                    /*foreach (var productItem in products)
                    {
                        //delete products
                        var permissionContextMappings = Context.PermissionContextMappings.Where(x => x.ContextId == productItem.Id).ToList();
                        //iterate through every row
                        foreach (var item in permissionContextMappings)
                        {

                            if (item.SubjectType == (int)PermissionContextMappingSubjectType.Group)
                            {
                                //get users in the permissiongroup
                                var permissionGroupUserMapping = Context.PermissionGroupUserMappings.Where(x => x.PermissionContextMappingId == item.Id).ToList();
                                //remove every row (users) from permissiongroup 
                                foreach (var user in permissionGroupUserMapping)
                                {
                                    Context.PermissionGroupUserMappings.Remove(user);
                                }
                            }
                        }                        
                        //remove permissioncontextmappings
                        Context.PermissionContextMappings.RemoveRange(permissionContextMappings);
                    }*/
                    Context.Products.RemoveRange(products);
                    Context.ServiceProviders.Remove(dbServiceProvider);
                    return Context.SaveChangesAsync().ContinueWith(x => x.Result > 0);
                }
                else
                {
                    return Task.FromResult(false);
                }
            }
        }

        //17. (IServiceProviderDataService) get service provider by ownerId
        public Task<List<TiamServiceProvider>> GetServiceProvidersByOwnerIdAsync()
        {
            throw new NotImplementedException();

        }

        #endregion

        #region PermissionTypes


        //10. (IPermissionService) create permission type
        public Task<bool> CreatePermissionsTypeAsync(PermissionsType permissionsType)
        {
            bool result = false;

            using (var transaction = Context.Database.BeginTransaction())
            {
                var existingPermission = Context.PermissionsTypes
                    .FirstOrDefault(x => x.PermissionName == permissionsType.PermissionName)?.PermissionName;

                if (existingPermission == null)
                {
                    //get all the permissiontypes for this context
                    var permissionTypes = new List<PermissionsType>();
                    var nextBitValue = 0.0;
                    permissionTypes = Context.PermissionsTypes
                        .Where(x => x.ContextId == permissionsType.ContextId)
                        .ToList();

                    //get the max value of the permissiontypes
                    if (permissionTypes != null)
                    {
                        //next bit value is the power of two of the count of the permissiontypes
                        nextBitValue = Math.Pow(2, permissionTypes.Count);
                    }
                    else
                    {
                        nextBitValue = Math.Pow(2, 0);
                    }
                    permissionsType.PermissionBit = (int)nextBitValue;
                    Context.PermissionsTypes.Add(permissionsType);
                    Context.SaveChanges();
                    transaction.Commit();
                    result = true;
                }
                else
                {
                    result = false;
                }

            }
            return Task.FromResult(result);

        }

        //11. (IPermissionService) get permission types for context
        public Task<List<PermissionsType>>? GetPermissionTypesByContextIdAsync(Guid contextId)
        {
            return Context.PermissionsTypes.Where(x => x.ContextId == contextId).ToListAsync();
        }

        public Task<int> GetPermissionFromPermissionType(PermissionsType pType)
        {
            if (Context.PermissionsTypes.FirstOrDefault(x => x.Id == pType.Id) != null)
            {
                return Task.FromResult(pType.PermissionBit);
            }
            else
            {
                return Task.FromResult(0);
            }
        }

        #endregion

        #region PermissionMappings

        public Task<List<AssignedPermissionModel>> GetPermissionsOfUserProductMappingsAndGroupsAsyncByContextId(Guid contextId)
        {
            List<AssignedPermissionModel> result = new List<AssignedPermissionModel>();

            var UserProductMappings = Context.UserProductMappings.Where(x => x.ProductId == contextId).ToListAsync();

            if (UserProductMappings.Result != null)
            {
                foreach (var item in UserProductMappings.Result)
                {
                    var mappingRow = Context.PermissionContextMappings.Where(x => x.SubjectId == item.Id).ToListAsync();
                    if (mappingRow.Result == null)
                    {
                        //user has no permission but is assigned... must be banned

                    }
                    else if (mappingRow.Result.Count > 1)
                    {
                        //user has been assigned more than onece to same context

                    }
                    else
                    {
                        foreach (var mapping in mappingRow.Result)
                        {
                            result.Add(new AssignedPermissionModel(item.ProductId, item.Id, mapping.SubjectType, item.UserId.ToString(), mapping.Permissions));
                        }
                    }

                }
            }

            var AssingedGroups = Context.PermissionGroups.Where(x => x.OwnerId == contextId).ToListAsync();

            if (AssingedGroups.Result != null)
            {
                foreach (var group in AssingedGroups.Result)
                {
                    var mappingRow = Context.PermissionContextMappings.Where(x => x.SubjectId == group.Id).ToListAsync();
                    if (mappingRow.Result == null)
                    {
                        //group has no permission but is assigned...

                    }
                    else if (mappingRow.Result.Count > 1)
                    {
                        //group has been assigned more than onece to same context

                    }
                    else
                    {
                        foreach (var mapping in mappingRow.Result)
                        {
                            result.Add(new AssignedPermissionModel(group.OwnerId, group.Id, mapping.SubjectType, group.GroupName, mapping.Permissions));
                        }
                    }

                }
            }
            foreach (var row in result)
            {
                Console.WriteLine($"GetPermissionsOfUserProductMappingsAndGroupsAsyncByContextId: {row.ContextId}, {row.SubjectId}, {row.SubjectType}, {row.Name}, {row.PermissionsValue}");
            }
            return Task.FromResult(result);
        }



        //12. (IPermissionService) get permission groups for context
        public Task<List<PermissionContextMapping>> GetPermissionsForContextByContextIdAsync(Guid contextId)
        {
            List<PermissionContextMapping> permissionContextMappings = new List<PermissionContextMapping>();
            //get all Groups where the contextId is the same
            var groups = Context.PermissionGroups.Where(x => x.OwnerId == contextId).Select(x => x.Id).ToHashSet();
            permissionContextMappings = Context.PermissionContextMappings.Where(x => groups.Contains(x.SubjectId)).ToList();

            //foreach (var item in groups.Result)
            //{
            //    //get permissioncontextmapping for the group if there is, so we know what permissions the group has
            //    var pCm = Context.PermissionContextMappings.FirstOrDefault(x => x.SubjectId == item.Id);
            //    permissionContextMappings.Add(pCm);
            //}
            return Task.FromResult(permissionContextMappings);
        }

        //9. (IPermissionService) add user to permission group
        public Task<bool> AddUserToPermissionGroupAsync(Guid permissionGroupId, Guid userId)
        {
            bool result = false;
            using (var transaction = Context.Database.BeginTransaction())
            {
                //do we need to check if PermissionContextMappingId exists?
                var permissionGroupUserMapping = new PermissionGroupUserMapping(userId, permissionGroupId);
                Context.PermissionGroupUserMappings.Add(permissionGroupUserMapping);
                Context.SaveChanges();
                transaction.Commit();
                result = true;
            }
            return Task.FromResult(result);
        }

        //8. (IPermissionService) create permission group
        public Task<bool> CreatePermissionGroupAsync(PermissionGroup permissionGroup, TiamServiceProvider serviceProvider)
        {
            bool result = false;
            using (var transaction = Context.Database.BeginTransaction())
            {
                var existingPermissionGroup = Context.PermissionGroups.FirstOrDefault(x => x.GroupName == permissionGroup.GroupName)?.GroupName;
                if (existingPermissionGroup == null)
                {
                    //create permission type 1 for the group
                    var permissionType = new PermissionsType(serviceProvider.Id, "View");
                    Context.CreatePermissionsType(permissionType);

                    //Create PermissionContextMapping for the group

                    //create Id for the group
                    Guid Id = Guid.NewGuid();
                    permissionGroup.Id = Id;
                    var permissionContextMapping = new PermissionContextMapping(serviceProvider.Id, Id, PermissionContextMappingSubjectType.Group, 1, true);
                    Context.CreatePermissionContextMapping(permissionContextMapping);
                    Context.CreatePermissionGroup(permissionGroup);
                    Context.SaveChanges();
                    transaction.Commit();
                    result = true;
                }
                else
                {
                    //group with same name already exists
                    result = false;
                }
            }
            return Task.FromResult(result);
        }

        public List<UserProductMapping> GetUserProductMappingsInPermissionGroupByGroupId(Guid groupId)
        {
            return Context.GetUserProductMappingsByPermissionGroupId(groupId).ToList();
            //List<UserProductMapping> userProductMappings = new List<UserProductMapping>();

            ////let's get the permissioncontextmapping for the group
            //var pCm = Context.PermissionContextMappings.FirstOrDefault(x => x.SubjectId == groupId);
            //Guid pCmId = pCm.Id;

            ////let's get the permissiongroupusermappings for the permissioncontextmapping
            //var pGum = Context.PermissionGroupUserMappings.Where(x => x.PermissionContextMappingId == pCmId).ToList();
            //if (pGum.Count > 0)
            //{
            //    foreach (var group in pGum)
            //    {
            //        userProductMappings.Add(Context.UserProductMappings.FirstOrDefault(x => x.Id == group.UserProductMappingId)); 
            //    }

            //}

            //return Task.FromResult(userProductMappings);
        }

        #endregion

        #region Products

        //* 21. (IServiceProviderDataService) delete product
        public Task<bool> DeleteProductByIdAsync(Guid productId)
        {
            return TransactionAsync(ctx =>
            {
                ctx.DeleteProductById(productId);

                return true;
            });
        }

        //4. (IPermissionService) AssignPermissionToUserForContextAsync
        public Task<bool> AssignPermissionToUserForContextAsync(UserProductMapping userProductMapping, PermissionsType permission)
        {
            var _assIgnedUser = Context.UserProductMappings.FirstOrDefault(x => x.Id == userProductMapping.Id);

            if (_assIgnedUser != null)
            {
                //user exists
                var _permissionInt = GetPermissionFromPermissionType(permission);

                var permissionContextMapping = Context.PermissionContextMappings.FirstOrDefault(x => x.SubjectId == userProductMapping.Id);
                var currentPermissions = permissionContextMapping.Permissions;
                var newPermissions = currentPermissions + _permissionInt.Result;
                permissionContextMapping.Permissions = newPermissions;
                return Context.SaveChangesAsync().ContinueWith(x => x.Result > 0);
            }
            else
            {
                //user does not exist, let's create it
                return Task.FromResult(false);
            }
        }

        #endregion


        #region UserProductMappings

        //23. (IServiceProviderDataService) Get Assigned Users By ProductId
        public Task<List<UserProductMapping>> GetUserProductMappingsByProductIdAsync(Guid productId)
        {
            return Context.UserProductMappings.Where(x => x.ProductId == productId).ToListAsync();
        }


        //24 . (IServiceProviderDataService) Remove Assigned Users By Product Id
        public Task RemoveUserProductMappingsByContextId(Guid productId)
        {
            using (var transaction = Context.Database.BeginTransaction())
            {
                var userProductMappings = Context.UserProductMappings.Where(x => x.ProductId == productId).ToList();
                //remove userProductMappings

                return Context.SaveChangesAsync().ContinueWith(x => x.Result > 0);

            }
        }

        //25. (IServiceProviderDataService) Remove Assigned from product by userProductMappingId
        public Task<bool> RemoveUserProductMappingAsync(UserProductMapping userProductMapping, bool removeFromGroups)
        {
            return TransactionAsync(ctx =>
            {
                var result = false;
                var userProductMappingToRemove = ctx.UserProductMappings.FirstOrDefault(x => x.Id == userProductMapping.Id);

                //remove userProductMappings
                if (userProductMappingToRemove == null) return false;


                if (removeFromGroups)
                {
                    //remove permissiongroupusermappings
                    ctx.RemoveAssingedUserFromPermissionGroups(userProductMappingToRemove.Id);
                }

                ctx.UserProductMappings.Remove(userProductMappingToRemove);


                return result;
            });
        }


        #endregion

    }
}
