using Brizco.Domain.Entities.Complexes;
using Brizco.Domain.Entities.Users;

namespace Brizco.Core.EntityServices;

public class UserService : IUserService
{
    private readonly ICurrentUserService _currentUserService;
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly RoleManager<ApplicationRole> _roleManager;
    private readonly ISender _sender;
    private readonly IRepositoryWrapper _repositoryWrapper;
    private readonly IJwtService _jwtService;

    public UserService(ICurrentUserService currentUserService,
        UserManager<ApplicationUser> userManager,
        RoleManager<ApplicationRole> roleManager,
        ISender sender,
        IRepositoryWrapper repositoryWrapper,
        IJwtService jwtService)
    {
        _currentUserService = currentUserService;
        _userManager = userManager;
        _roleManager = roleManager;
        _sender = sender;
        _repositoryWrapper = repositoryWrapper;
        _jwtService = jwtService;
    }


    public async Task<ProfileResponseDto> GetUserProfileAsync(CancellationToken cancellationToken)
    {
        if (!Guid.TryParse(_currentUserService.UserId, out var userId))
            throw new AppException("Wrong Token", ApiResultStatusCode.UnAuthorized);
        var user = await _userManager.FindByIdAsync(userId.ToString());
        if (user == null)
            throw new AppException("User NotFound", ApiResultStatusCode.NotFound);
        if (user.SignUpStatus < SignUpStatus.ComplexCreated)
            throw new UnauthorizedAccessException("SignUp is not completed");

        var response = new ProfileResponseDto();

        var userSDto = user.AdaptToSDto();

        var complexUsers = await GetUserRolesAsync(cancellationToken);
        response.User = userSDto;
        if (user.SelectedComplexUserRoleId != Guid.Empty)
        {
            var complexUserRole = complexUsers.FirstOrDefault(cu => cu.Id == user.SelectedComplexUserRoleId);

            userSDto.SelectedComplexName = complexUserRole!.ComplexName;
            userSDto.SelectedRoleName = complexUserRole!.RoleName;
            response.User.SelectedRoleId = complexUserRole!.Id;
            var role = await _roleManager.FindByIdAsync(complexUserRole.RoleId.ToString());
            if (role != null)
            {
                var roleClaims = await _roleManager.GetClaimsAsync(role);
                response.Permissions = roleClaims.Where(c => c.Type == "Permission").Select(c => c.Value).ToList();
            }
        }
        else
        {
            var complexUserRole = complexUsers.FirstOrDefault();
            if (complexUserRole != null)
            {
                user.SelectedComplexUserRoleId = complexUserRole.Id;
                await _userManager.UpdateAsync(user);
                userSDto.SelectedComplexName = complexUserRole!.ComplexName;
                userSDto.SelectedRoleName = complexUserRole!.RoleName;
                response.User.SelectedRoleId = complexUserRole!.Id;
                var role = await _roleManager.FindByIdAsync(complexUserRole.RoleId.ToString());
                if (role != null)
                {
                    var roleClaims = await _roleManager.GetClaimsAsync(role);
                    response.Permissions = roleClaims.Where(c => c.Type == "Permission").Select(c => c.Value).ToList();
                }
            }

        }

        response.Roles = complexUsers;
        return response;
    }

    public async Task<AccessToken<ApplicationUserSDto, ComplexUserRoleSDto>> ChangeUserRoleAsync(Guid roleId, CancellationToken cancellationToken)
    {

        if (!Guid.TryParse(_currentUserService.UserId, out var userId))
            throw new AppException("Wrong Token", ApiResultStatusCode.UnAuthorized);

        if (!Guid.TryParse(_currentUserService.ComplexId, out var complexId))
            throw new AppException("Wrong Token", ApiResultStatusCode.UnAuthorized);

        AccessToken<ApplicationUserSDto, ComplexUserRoleSDto> jwt;


        var complexUser = await _repositoryWrapper.SetRepository<ComplexUser>()
            .TableNoTracking
            .Where(mcu => mcu.UserId == userId && mcu.ComplexId == complexId)
            .Select(ComplexUserMapper.ProjectToSDto)
            .FirstOrDefaultAsync(cancellationToken);
        if (complexUser == null)
            throw new AppException("User role is wrong", ApiResultStatusCode.BadRequest);

        var complexUserRole = await _repositoryWrapper.SetRepository<ComplexUserRole>()
            .TableNoTracking
            .Where(c => c.Id == roleId)
            .Select(ComplexUserRoleMapper.ProjectToSDto)
            .FirstOrDefaultAsync(cancellationToken);

        if (complexUserRole == null)
            throw new AppException("Role not found", ApiResultStatusCode.NotFound);

        var user = await _userManager.FindByIdAsync(userId.ToString());
        if (user == null)
            throw new AppException("User not found", ApiResultStatusCode.NotFound);

        user.SelectedComplexUserRoleId = complexUserRole.Id;
        await _userManager.UpdateAsync(user);
        jwt = (await _jwtService.Generate<ApplicationUserSDto, ApplicationUser>(user, complexUser.ComplexId, complexUserRole.RoleId)).Adapt<AccessToken<ApplicationUserSDto, ComplexUserRoleSDto>>();
        jwt.User.SelectedComplexName = complexUser.ComplexName;
        jwt.User.SelectedRoleName = complexUserRole.RoleName;
        jwt.User.SelectedRoleId = complexUserRole.Id;
        jwt.Roles = await GetUserRolesAsync(cancellationToken);
        return jwt;
    }

    public async Task<List<ComplexUserRoleSDto>> GetUserRolesAsync(CancellationToken cancellationToken)
    {
        if (!Guid.TryParse(_currentUserService.UserId, out var userId))
            throw new AppException("Wrong Token", ApiResultStatusCode.UnAuthorized);
        var user = await _userManager.FindByIdAsync(userId.ToString());
        if (user == null)
            throw new AppException("User NotFound", ApiResultStatusCode.NotFound);
        var response = new List<ComplexUserRoleSDto>();
        var complexUsers = await _repositoryWrapper.SetRepository<ComplexUser>()
            .TableNoTracking
            .Where(mcu => mcu.UserId == user.Id)
            .Select(ComplexUserMapper.ProjectToSDto)
            .ToListAsync(cancellationToken);
        if (complexUsers.Count == 0)
            return response;

        foreach (var complexUser in complexUsers)
        {
            var complexUserRoles = await _repositoryWrapper.SetRepository<ComplexUserRole>()
                .TableNoTracking
                .Where(c => c.ComplexUserId == complexUser.Id)
                .Select(ComplexUserRoleMapper.ProjectToSDto)
                .ToListAsync(cancellationToken);
            foreach (var userRole in complexUserRoles)
            {
                userRole.ComplexName = complexUser.ComplexName;
                response.Add(userRole);
            }
        }

        return response;
    }

    public async Task<List<ComplexUserRoleSDto>> GetUserRolesAsync(Guid userId, CancellationToken cancellationToken)
    {
        var user = await _userManager.FindByIdAsync(userId.ToString());
        if (user == null)
            throw new AppException("User NotFound", ApiResultStatusCode.NotFound);
        var response = new List<ComplexUserRoleSDto>();
        var complexUsers = await _repositoryWrapper.SetRepository<ComplexUser>()
            .TableNoTracking
            .Where(mcu => mcu.UserId == user.Id)
            .Select(ComplexUserMapper.ProjectToSDto)
            .ToListAsync(cancellationToken);
        if (complexUsers.Count == 0)
            return response;

        foreach (var complexUser in complexUsers)
        {
            var complexUserRoles = await _repositoryWrapper.SetRepository<ComplexUserRole>()
                .TableNoTracking
                .Where(c => c.ComplexUserId == complexUser.Id)
                .Select(ComplexUserRoleMapper.ProjectToSDto)
                .ToListAsync(cancellationToken);
            foreach (var userRole in complexUserRoles)
            {
                userRole.ComplexName = complexUser.ComplexName;
                response.Add(userRole);
            }
        }

        return response;
    }

    public async Task<List<ComplexUserSDto>> GetUsersAsync(int page = 0, CancellationToken cancellationToken = default)
    {
        if (_currentUserService.ComplexId.IsNullOrEmpty())
            throw new AppException("Wrong authorize token , ComplexId needed");
        if (!Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
            throw new AppException("Wrong authorize token , ComplexId needed");
        var complexUsers = await _sender.Send(new GetComplexUsersQuery(complexId.ToString(), page), cancellationToken);

        return complexUsers;
    }

    public async Task<ApplicationUserSDto> GetUserAsync(Guid userId)
    {
        var user = await _userManager.FindByIdAsync(userId.ToString());
        if (user == null)
            throw new AppException("User not found", ApiResultStatusCode.NotFound);
        var dto = user.AdaptToSDto();
        if (_currentUserService.ComplexId == null || !Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
        {
            var roles = await _userManager.GetRolesAsync(user);
            foreach (var roleName in roles)
            {
                var role = await _roleManager.FindByNameAsync(roleName);
                if (role != null)
                    dto.RoleIds.Add(role.Id);
            }
        }
        else
        {
            var complexUser = await _repositoryWrapper.SetRepository<ComplexUser>().TableNoTracking
                .FirstOrDefaultAsync(c => c.UserId == userId && c.ComplexId == complexId);

            if (complexUser == null)
                throw new AppException("ComplexUser not found", ApiResultStatusCode.NotFound);

            var complexUserRoles = await _repositoryWrapper.SetRepository<ComplexUserRole>()
                .TableNoTracking
                .Where(cur => cur.ComplexUserId == complexUser.Id)
                .ToListAsync();
            foreach (var userRole in complexUserRoles)
            {
                dto.RoleIds.Add(userRole.RoleId);
            }
        }
        return dto;
    }

    public async Task<ApplicationUser> CreateUserAsync(string phoneNumber)
    {
        var user = new ApplicationUser
        {
            UserName = phoneNumber,
            PhoneNumber = phoneNumber,
            SignUpStatus = SignUpStatus.StartSignOn
        };
        var result = await _userManager.CreateAsync(user);
        if (!result.Succeeded)
            throw new AppException(string.Join('|', result.Errors));
        return user;
    }

    public async Task<ApplicationUser> CreateUserAsync(UserActionRequestDto request, CancellationToken cancellationToken)
    {
        if (_currentUserService.ComplexId.IsNullOrEmpty())
            throw new AppException("Wrong authorize token , ComplexId needed");
        if (!Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
            throw new AppException("Wrong authorize token , ComplexId needed");

        var user = await _userManager.FindByNameAsync(request.PhoneNumber);
        if (user == null)
        {
            user = new ApplicationUser
            {
                UserName = request.PhoneNumber,
                PhoneNumber = request.PhoneNumber,
                FirstName = request.FirstName,
                LastName = request.LastName,
                NationalId = request.NationalId,
                BirthDate = DateTimeExtensions.UnixTimeStampToDateTime(request.BirthDateTimeStamp),
                Gender = request.Gender,
                SignUpStatus = SignUpStatus.SignUpCompleted,
                PhoneNumberConfirmed = true
            };

            if (!request.Password.IsNullOrEmpty())
            {
                var result = await _userManager.CreateAsync(user, request.Password);
                if (!result.Succeeded)
                    throw new AppException(string.Join('|', result.Errors.Select(e => e.Description)));
            }
            else
            {
                var result = await _userManager.CreateAsync(user);
                if (!result.Succeeded)
                    throw new AppException(string.Join('|', result.Errors.Select(e => e.Description)));
            }
        }

        await _sender.Send(new CreateComplexUserCommand(complexId, user.Id, request.RoleIds), cancellationToken);
        return user;
    }

    public async Task<bool> EditUserAsync(UserActionRequestDto request, CancellationToken cancellationToken)
    {
        if (_currentUserService.ComplexId.IsNullOrEmpty())
            throw new AppException("Wrong authorize token , ComplexId needed");
        if (!Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
            throw new AppException("Wrong authorize token , ComplexId needed");
        if (request.UserId == Guid.Empty)
            throw new AppException("Wrong authorize token , UserId needed");

        var user = await _userManager.FindByIdAsync(request.UserId.ToString());
        if (user == null)
            throw new AppException("User not found", ApiResultStatusCode.NotFound);
        user.LastName = request.LastName;
        user.FirstName = request.FirstName;
        user.UserName = request.PhoneNumber;
        user.PhoneNumber = request.PhoneNumber;
        user.FirstName = request.FirstName;
        user.LastName = request.LastName;
        user.NationalId = request.NationalId;
        user.BirthDate = DateTimeExtensions.UnixTimeStampToDateTime(request.BirthDateTimeStamp);
        user.Gender = request.Gender;

        var result = await _userManager.UpdateAsync(user);
        if (!result.Succeeded)
            throw new AppException(string.Join('|', result.Errors.Select(e => e.Description)));
        if (!request.Password.IsNullOrEmpty())
        {
            if (await _userManager.HasPasswordAsync(user))
                await _userManager.RemovePasswordAsync(user);

            var addPassResult = await _userManager.AddPasswordAsync(user, request.Password);
            if (!addPassResult.Succeeded)
                throw new AppException(string.Join('|', addPassResult.Errors.Select(e => e.Description)));
        }

        await _sender.Send(new UpdateComplexUserCommand(user.Id, complexId, request.RoleIds), cancellationToken);
        return true;
    }

    public async Task<bool> EditUserProfileAsync(UserActionRequestDto request, CancellationToken cancellationToken)
    {
        if (_currentUserService.UserId == null)
            throw new AppException("Wrong authorize token , UserId needed");

        var user = await _userManager.FindByIdAsync(_currentUserService.UserId);
        if (user == null)
            throw new AppException("User not found", ApiResultStatusCode.NotFound);
        user.LastName = request.LastName;
        user.FirstName = request.FirstName;
        user.UserName = request.PhoneNumber;
        user.PhoneNumber = request.PhoneNumber;
        user.FirstName = request.FirstName;
        user.LastName = request.LastName;
        user.NationalId = request.NationalId;
        user.BirthDate = DateTimeExtensions.UnixTimeStampToDateTime(request.BirthDateTimeStamp);
        user.Gender = request.Gender;

        var result = await _userManager.UpdateAsync(user);
        if (!result.Succeeded)
            throw new AppException(string.Join('|', result.Errors.Select(e => e.Description)));
        if (!request.Password.IsNullOrEmpty())
        {
            if (await _userManager.HasPasswordAsync(user))
                await _userManager.RemovePasswordAsync(user);

            var addPassResult = await _userManager.AddPasswordAsync(user, request.Password);
            if (!addPassResult.Succeeded)
                throw new AppException(string.Join('|', addPassResult.Errors.Select(e => e.Description)));
        }

        return true;
    }

    public async Task<bool> RemoveUserAsync(Guid userId, CancellationToken cancellationToken)
    {
        if (_currentUserService.ComplexId.IsNullOrEmpty())
            throw new AppException("Wrong authorize token , ComplexId needed");
        if (!Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
            throw new AppException("Wrong authorize token , ComplexId needed");
        var user = await _userManager.FindByIdAsync(userId.ToString());
        if (user == null)
            throw new AppException("User not found", ApiResultStatusCode.NotFound);
        var roles = await _userManager.GetRolesAsync(user);
        await _userManager.RemoveFromRolesAsync(user, roles);
        await _sender.Send(new DeleteComplexUserCommand(userId, complexId), cancellationToken);
        var removeResult = await _userManager.DeleteAsync(user);
        if (!removeResult.Succeeded)
            throw new AppException(string.Join('|', removeResult.Errors.Select(e => e.Description)));
        return true;
    }

    public async Task<bool> RemoveUserFromComplexAsync(Guid userId, CancellationToken cancellationToken)
    {
        if (_currentUserService.ComplexId.IsNullOrEmpty())
            throw new AppException("Wrong authorize token , ComplexId needed");
        if (!Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
            throw new AppException("Wrong authorize token , ComplexId needed");
        var user = await _userManager.FindByIdAsync(userId.ToString());
        if (user == null)
            throw new AppException("User not found", ApiResultStatusCode.NotFound);
        await _sender.Send(new DeleteComplexUserCommand(userId, complexId), cancellationToken);
        return true;
    }



    public async Task<List<ApplicationRole>> GetRolesAsync(int page = 0, CancellationToken cancellationToken = default)
    {
        if (_currentUserService.ComplexId.IsNullOrEmpty())
            throw new AppException("Wrong authorize token , ComplexId needed");
        if (!Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
            throw new AppException("Wrong authorize token , ComplexId needed");

        var roles = await _roleManager.Roles
            .Where(r => r.ComplexId == complexId)
            .Skip(page * 15)
            .Take(15)
            .ToListAsync(cancellationToken);
        return roles;
    }

    public async Task<RoleActionRequestDto> GetRoleAsync(Guid roleId)
    {
        var role = (await _roleManager.FindByIdAsync(roleId.ToString()));
        if (role == null)
            throw new AppException("نقش پیدا نشد", ApiResultStatusCode.NotFound);

        var roleDto = role.Adapt<RoleActionRequestDto>();
        roleDto.RoleId = roleId;
        roleDto.Permissions = (await _roleManager.GetClaimsAsync(role))
            .Where(c => c.Type == CustomClaimType.Permission)
            .Select(c => c.Value)
            .ToList();

        return roleDto;
    }

    public async Task<ApplicationRole> CreateRoleAsync(RoleActionRequestDto request)
    {
        if (_currentUserService.ComplexId.IsNullOrEmpty())
            throw new AppException("Wrong authorize token , ComplexId needed");
        if (!Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
            throw new AppException("Wrong authorize token , ComplexId wrong");
        if (request.EnglishName.IsNullOrEmpty())
            throw new AppException("لطفا نام انگلیسی را وارد کنید");
        var applicationRole = new ApplicationRole
        {
            ComplexId = complexId,
            EnglishName = request.EnglishName,
            PersianName = request.PersianName,
            Description = request.Description,
            Name = $"{request.EnglishName}_{complexId.ToString()}"
        };
        var createRoleResult = await _roleManager.CreateAsync(applicationRole);
        if (!createRoleResult.Succeeded)
            throw new AppException(string.Join('|', createRoleResult.Errors));

        foreach (var claim in request.Permissions)
            await _roleManager.AddClaimAsync(applicationRole, new Claim(CustomClaimType.Permission, claim));
        return applicationRole;
    }

    public async Task<bool> EditRoleAsync(RoleActionRequestDto request)
    {
        if (_currentUserService.ComplexId.IsNullOrEmpty())
            throw new AppException("Wrong authorize token , ComplexId needed");
        if (!Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
            throw new AppException("Wrong authorize token , ComplexId wrong");
        if (request.EnglishName.IsNullOrEmpty())
            throw new AppException("لطفا نام انگلیسی را وارد کنید");
        var applicationRole = await _roleManager.FindByIdAsync(request.RoleId.ToString());
        if (applicationRole == null)
            throw new AppException("نقش پیدا نشد");

        applicationRole.ComplexId = complexId;
        applicationRole.EnglishName = request.EnglishName;
        applicationRole.PersianName = request.PersianName;
        applicationRole.Description = request.Description;
        applicationRole.Name = $"{request.EnglishName}_{complexId.ToString()}";

        var createRoleResult = await _roleManager.UpdateAsync(applicationRole);
        if (!createRoleResult.Succeeded)
            throw new AppException(string.Join('|', createRoleResult.Errors));
        var roleClaims = (await _roleManager.GetClaimsAsync(applicationRole)).Where(c => c.Type == CustomClaimType.Permission).ToList();
        foreach (var roleClaim in roleClaims.ToList())
        {
            if (request.Permissions.Contains(roleClaim.Value))
            {
                roleClaims.Remove(roleClaim);
                request.Permissions.Remove(roleClaim.Value);
            }
        }

        foreach (var claim in request.Permissions)
            await _roleManager.AddClaimAsync(applicationRole, new Claim(CustomClaimType.Permission, claim));

        foreach (var claim in roleClaims)
            await _roleManager.RemoveClaimAsync(applicationRole, claim);

        return true;
    }

    public async Task<bool> RemoveRoleAsync(Guid roleId)
    {
        var applicationRole = await _roleManager.FindByIdAsync(roleId.ToString());
        if (applicationRole == null)
            throw new AppException("User not found", ApiResultStatusCode.NotFound);
        var claims = await _roleManager.GetClaimsAsync(applicationRole);
        foreach (var claim in claims)
            await _roleManager.RemoveClaimAsync(applicationRole, claim);
        var users = await _userManager.GetUsersInRoleAsync(applicationRole.Name);
        foreach (var user in users)
            await _userManager.RemoveFromRoleAsync(user, applicationRole.Name);

        var complexRoles = await _repositoryWrapper.SetRepository<ComplexUserRole>()
            .TableNoTracking
            .Where(r => r.RoleId == applicationRole.Id)
            .ToListAsync();
        foreach (var complexRole in complexRoles)
        {
            _repositoryWrapper.SetRepository<ComplexUserRole>()
                .HardDelete(complexRole);
            await _repositoryWrapper.SaveChangesAsync(default);
        }


        var removeResult = await _roleManager.DeleteAsync(applicationRole);
        if (!removeResult.Succeeded)
            throw new AppException(string.Join('|', removeResult.Errors.Select(e => e.Description)));
        return true;
    }

    public List<ClaimDto> GetPermissions()
    {
        return ApplicationClaims.AllClaimDtos;
    }
}