using AsbCloudApp.Comparators;
using AsbCloudApp.Data;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using Mapster;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services
{
    public class UserRoleService : IUserRoleService
    {
        private readonly CacheTable<UserRole> cacheUserRoles;
        private readonly CacheTable<RelationUserRolePermission> cacheRelationUserRolePermissions;
        private readonly CacheTable<RelationUserRoleUserRole> cacheRelationUserRoleUserRole;

        public ISet<string> Includes { get; } = new SortedSet<string>();

        public UserRoleService(IAsbCloudDbContext context, CacheDb cacheDb)
        {
            cacheUserRoles = cacheDb.GetCachedTable<UserRole>((AsbCloudDbContext)context, nameof(UserRole.RelationUserRolePermissions), nameof(UserRole.RelationUserRoleUserRoles));
            cacheRelationUserRolePermissions = cacheDb.GetCachedTable<RelationUserRolePermission>((AsbCloudDbContext)context, nameof(RelationUserRolePermission.Permission));
            cacheRelationUserRoleUserRole = cacheDb.GetCachedTable<RelationUserRoleUserRole>((AsbCloudDbContext)context, nameof(RelationUserRoleUserRole.IncludeRole), nameof(RelationUserRoleUserRole.Role));
        }

        public async Task<int> InsertAsync(UserRoleDto dto, CancellationToken token = default)
        {
            var entity = dto.Adapt<UserRole>();
            var updatedEntity = await cacheUserRoles.InsertAsync(entity, token)
                .ConfigureAwait(false);
            dto.Id = updatedEntity.Id;
            await UpdatePermissionsAsync(dto, token);
            await UpdateIncludedRolesAsync(dto, token);

            await cacheUserRoles.RefreshAsync(true, token)
                .ConfigureAwait(false);
            return updatedEntity?.Id ?? 0;
        }

        public Task<int> InsertRangeAsync(IEnumerable<UserRoleDto> dtos, CancellationToken token = default)
        {
            throw new NotImplementedException();
            //var entities = dtos.Adapt<UserRole>();
            //return await cacheUserRoles.InsertAsync(entities, token).ConfigureAwait(false);
        }

        public async Task<IEnumerable<UserRoleDto>> GetAllAsync(CancellationToken token = default)
        {
            var entities = await cacheUserRoles.WhereAsync(token)
                .ConfigureAwait(false);
            var dtos = entities?.Select(Convert);
            return dtos;
        }
        public UserRoleDto Get(int id)
        {
            var entity = cacheUserRoles.FirstOrDefault(r => r.Id == id);
            if (entity is null)
                return null;
            var dto = Convert(entity);
            return dto;
        }

        public async Task<UserRoleDto> GetAsync(int id, CancellationToken token = default)
        {
            var entity = await cacheUserRoles.FirstOrDefaultAsync(r => r.Id == id, token)
                .ConfigureAwait(false);
            if (entity is null)
                return null;
            var dto = Convert(entity);
            return dto;
        }

        public async Task<UserRoleDto> GetByNameAsync(string name, CancellationToken token = default)
        {
            var entity = await cacheUserRoles.FirstOrDefaultAsync(r => r.Caption == name, token)
                .ConfigureAwait(false);
            if (entity is null)
                return null;
            var dto = Convert(entity);
            return dto;
        }

        public async Task<IEnumerable<UserRoleDto>> GetByNamesAsync(IEnumerable<string> names, CancellationToken token = default)
        {
            if (names?.Any() != true)
                return null;
            var entities = await cacheUserRoles.WhereAsync(r => names.Contains(r.Caption), token)
                .ConfigureAwait(false);
            if (entities?.Count() != names.Count())
                throw new ArgumentInvalidException("Invalid role names", nameof(names));
            var dtos = entities.Select(Convert);
            return dtos;
        }

        public async Task<int> UpdateAsync(UserRoleDto dto, CancellationToken token = default)
        {
            var entity = Convert(dto);
            await UpdatePermissionsAsync(dto, token);
            await UpdateIncludedRolesAsync(dto, token);

            var result = await cacheUserRoles.UpsertAsync(entity, token)
                .ConfigureAwait(false);
            return result;
        }

        public IEnumerable<UserRoleDto> GetNestedById(int id, int recursionLevel = 7)
        {
            var role = cacheUserRoles.FirstOrDefault(r => r.Id == id);
            if (role is null)
                return null;
            var dto = Convert(role);
            var roles = new SortedSet<UserRoleDto>(ComparerIId.GetInstance()) { dto };

            if (recursionLevel <= 0 || role.RelationUserRoleUserRoles?.Any() != true)
                return roles;

            foreach (var relation in role.RelationUserRoleUserRoles)
            {
                var nestedRoles = GetNestedById(relation.IdInclude, --recursionLevel);
                if (nestedRoles?.Any() != true)
                    continue;
                foreach (var nestedRole in nestedRoles)
                    roles.Add(nestedRole);
            }
            return roles;
        }

        private async Task UpdatePermissionsAsync(UserRoleDto dto, CancellationToken token)
        {
            if (dto?.Permissions is null)
                return;

            await cacheRelationUserRolePermissions.RemoveAsync(r => r.IdUserRole == dto.Id, token)
                .ConfigureAwait(false);

            if (dto.Permissions.Any())
            {
                var newRelationRoleToPermission = dto.Permissions.Select(p => new RelationUserRolePermission
                {
                    IdPermission = p.Id,
                    IdUserRole = dto.Id,
                });

                await cacheRelationUserRolePermissions.InsertAsync(newRelationRoleToPermission, token)
                    .ConfigureAwait(false);
            }
        }

        private async Task UpdateIncludedRolesAsync(UserRoleDto dto, CancellationToken token)
        {
            if (dto?.Roles is null)
                return;

            await cacheRelationUserRoleUserRole.RemoveAsync(rel => rel.Id == dto.Id, token);

            if (dto.Roles.Any())
            {
                var newRelations = dto.Roles.Select(r => new RelationUserRoleUserRole { Id = dto.Id, IdInclude = r.Id });
                await cacheRelationUserRoleUserRole.UpsertAsync(newRelations, token);
            }
        }

        public Task<int> DeleteAsync(int id, CancellationToken token = default)
        => cacheUserRoles.RemoveAsync(r => r.Id == id, token);

        public Task<int> DeleteAsync(IEnumerable<int> ids, CancellationToken token = default)
        => cacheUserRoles.RemoveAsync(r => ids.Contains(r.Id), token);

        public bool HasPermission(IEnumerable<int> rolesIds, string permissionName)
        {
            var permissionInfo = cacheRelationUserRolePermissions
                .FirstOrDefault(p => p.Permission?.Name.ToLower() == permissionName.ToLower())
                ?.Permission;

            if (permissionInfo is null)
                return false;

            if (rolesIds.Contains(1))
                return true;

            var idPermissionInfo = permissionInfo.Id;
            var roles = cacheUserRoles.Where(r => rolesIds.Contains(r.Id));
            foreach (var role in roles)
                if (HasPermission(role, idPermissionInfo))
                    return true;
            return false;
        }

        private bool HasPermission(UserRole userRole, int idPermission, int recursionLevel = 7)
        {
            if (userRole.RelationUserRolePermissions.Any(p => p.IdPermission == idPermission))
                return true;

            if (recursionLevel <= 0 || userRole.RelationUserRoleUserRoles?.Any() != true)
                return false;

            foreach (var relation in userRole.RelationUserRoleUserRoles)
            {
                var includedRole = cacheUserRoles.First(p => p.Id == relation.IdInclude);
                if (HasPermission(includedRole, idPermission, --recursionLevel))
                    return true;
            }
            return false;
        }

        private static UserRole Convert(UserRoleDto dto)
        {
            var entity = dto.Adapt<UserRole>();
            return entity;
        }

        private UserRoleDto Convert(UserRole entity)
        {
            var dto = entity.Adapt<UserRoleDto>();
            if (entity.RelationUserRolePermissions?.Any() == true)
            {
                dto.Permissions = cacheRelationUserRolePermissions
                    .Where(r => entity.Id == r.IdUserRole)
                    .Select(r => Convert(r.Permission));
            }

            if (entity.RelationUserRoleUserRoles?.Any() == true)
            {
                dto.Roles = entity.RelationUserRoleUserRoles.Select(rel =>
                {
                    var includedRole = cacheUserRoles.First(r => r.Id == rel.IdInclude);
                    return Convert(includedRole);
                }).ToList();
            }
            return dto;
        }

        private static PermissionDto Convert(Permission entity)
        {
            var dto = entity.Adapt<PermissionDto>();
            return dto;
        }
    }
}