using Microsoft.AspNetCore.Identity;
using TeleWave.Application.Admin.Roles;
using TeleWave.Application.Admin.Users;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;

namespace TeleWave.Infrastructure.Identity;

internal sealed class RoleService(
    RoleManager<AppRole> roleManager,
    UserManager<AppUser> userManager
) : IRoleService
{
    public async Task<Result<RoleDto>> CreateRoleAsync(
        string name,
        CancellationToken cancellationToken
    )
    {
        if (await roleManager.RoleExistsAsync(name))
            return Result.Failure<RoleDto>(RoleErrors.DuplicateName);

        var role = new AppRole(name) { IsSystem = false };
        var result = await roleManager.CreateAsync(role);
        if (!result.Succeeded)
        {
            return Result.Failure<RoleDto>(
                Error.Validation(
                    "Roles.CreateFailed",
                    string.Join("; ", result.Errors.Select(e => e.Description))
                )
            );
        }

        return Result.Success(ToDto(role));
    }

    public async Task<Result<RoleDto>> UpdateRoleAsync(
        Guid roleId,
        string name,
        CancellationToken cancellationToken
    )
    {
        var role = await roleManager.FindByIdAsync(roleId.ToString());
        if (role is null)
            return Result.Failure<RoleDto>(RoleErrors.NotFound);

        if (role.IsSystem)
            return Result.Failure<RoleDto>(RoleErrors.CannotModifySystemRole);

        if (await roleManager.FindByNameAsync(name) is { } existing && existing.Id != roleId)
            return Result.Failure<RoleDto>(RoleErrors.DuplicateName);

        role.Name = name;
        await roleManager.UpdateAsync(role);

        return Result.Success(ToDto(role));
    }

    public async Task<Result> DeleteRoleAsync(Guid roleId, CancellationToken cancellationToken)
    {
        var role = await roleManager.FindByIdAsync(roleId.ToString());
        if (role is null)
            return Result.Failure(RoleErrors.NotFound);

        if (role.IsSystem)
            return Result.Failure(RoleErrors.CannotModifySystemRole);

        var usersInRole = await userManager.GetUsersInRoleAsync(role.Name!);
        if (usersInRole.Count > 0)
            return Result.Failure(RoleErrors.RoleInUse);

        await roleManager.DeleteAsync(role);
        return Result.Success();
    }

    public Task<IReadOnlyList<RoleDto>> ListRolesAsync(CancellationToken cancellationToken)
    {
        IReadOnlyList<RoleDto> roles = roleManager
            .Roles.OrderBy(r => r.Name)
            .Select(r => new RoleDto(r.Id, r.Name!, r.IsSystem))
            .ToList();
        return Task.FromResult(roles);
    }

    public async Task<Result> ChangeUserRoleAsync(
        Guid userId,
        Guid roleId,
        CancellationToken cancellationToken
    )
    {
        var user = await userManager.FindByIdAsync(userId.ToString());
        if (user is null)
            return Result.Failure(UserErrors.NotFound);

        var role = await roleManager.FindByIdAsync(roleId.ToString());
        if (role is null)
            return Result.Failure(RoleErrors.NotFound);

        var currentRoles = await userManager.GetRolesAsync(user);
        var wasAdmin = currentRoles.Contains(RoleNames.Admin, StringComparer.OrdinalIgnoreCase);
        var staysAdmin = role.Name!.Equals(RoleNames.Admin, StringComparison.OrdinalIgnoreCase);

        if (wasAdmin && !staysAdmin)
        {
            var adminCount = (await userManager.GetUsersInRoleAsync(RoleNames.Admin)).Count;
            if (adminCount <= 1)
                return Result.Failure(RoleErrors.CannotRemoveLastAdmin);
        }

        if (currentRoles.Count > 0)
            await userManager.RemoveFromRolesAsync(user, currentRoles);

        await userManager.AddToRoleAsync(user, role.Name);

        return Result.Success();
    }

    private static RoleDto ToDto(AppRole role) => new(role.Id, role.Name!, role.IsSystem);
}
