@namespace OpenArchival.Blazor.AdminPages.Shared

@using OpenArchival.Blazor.AdminPages.Shared;
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.EntityFrameworkCore
@using MudBlazor
@using MudExtensions
@using OpenArchival.Blazor.ArchiveSearch
@using OpenArchival.DataAccess
@using Microsoft.AspNetCore.Identity


<MudDataGrid T="UserDto"
             MultiSelection=false
             Filterable=false
             SelectOnRowClick=true
             ServerData="new Func<GridState<UserDto>, Task<GridData<UserDto>>>(ServerReload)"
             @ref=@DataGrid
             @bind-SelectedItems="_selectedItems"
             Comparer="_comparer">

    <ToolBarContent>
        
        <MudSpacer /> 

        <MudButton Variant="Variant.Filled"
                   StartIcon="@Icons.Material.Filled.Add"
                   Color="Color.Primary"
                   OnClick="CreateNewUser">New User</MudButton>
    </ToolBarContent>
    
    <Columns>
        <PropertyColumn 
            Title="Id"
            Property="x=>x.Id"
            Filterable="false"/>

        <PropertyColumn
            Title="Username"
            Property="x=>x.Username"
            Filterable="false"/>

        <PropertyColumn 
            Title="Roles" 
            Property="x=>x.RolesDisplay"
            Filterable="false"/>

        <TemplateColumn Title="Edit"> 
            <CellTemplate>
                <MudIconButton 
                    Color="Color.Primary"
                    Icon="@Icons.Material.Filled.Edit" 
                    Variant="Variant.Filled"
                    OnClick="@(() => OnEditClicked(context.Item))">Edit</MudIconButton>
            </CellTemplate>
        </TemplateColumn>

        <TemplateColumn Title="Delete">
            <CellTemplate>
                <MudIconButton
                    Color="Color.Warning"
                    Icon="@Icons.Material.Filled.Delete"
                    Variant="Variant.Filled"
                    OnClick="@(() => OnDeleteClicked(context.Item))"
                >
                    
                </MudIconButton>

            </CellTemplate>
        </TemplateColumn>
       
    </Columns>

    <PagerContent>
        <MudDataGridPager T="UserDto"/>
    </PagerContent>
</MudDataGrid>


@inject IArtifactGroupingProvider GroupingProvider;
@inject IDialogService DialogService;
@inject IArtifactGroupingProvider GroupingProvider;
@inject IDbContextFactory<ApplicationDbContext> ContextFactory;
@inject ISnackbar Snackbar;
@inject NavigationManager NavigationManager;
@inject UserManager<ApplicationUser> UserManager;
@inject RoleManager<IdentityRole> RoleManager;
@inject IDbContextFactory<ApplicationDbContext> ContextFactory;

@code 
{
    public class UserRowElementComparer: IEqualityComparer<UserDto>
    {
        public bool Equals(UserDto? x, UserDto? y)
        {
            if (ReferenceEquals(x, y)) return true;
            if (x is null || y is null) return false;
            return x.Id == y.Id;
        }

        public int GetHashCode(UserDto obj)
        {
            return obj.Id.GetHashCode();
        }
    }

    private UserRowElementComparer _comparer = new();

    public MudDataGrid<UserDto> DataGrid { get; set; } = default!;

    private HashSet<UserDto> _selectedItems = new(new UserRowElementComparer());

    private string _searchString { get; set; } = "";

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        StateHasChanged();
    }

    private async Task CreateNewUser(MouseEventArgs args)
    {

        var options = new DialogOptions { CloseOnEscapeKey = true, BackdropClick = false};
        var dialog = await DialogService.ShowAsync<UserManagementDialog>("Create a User", options);

        var newUserDto = await dialog.Result;

        if (newUserDto is not null && !newUserDto.Canceled)
        {
            var userDto = (UserDto?)newUserDto.Data;
            if (userDto is null)
            {
                Snackbar.Add("The new user dialog did not return a user model.", Severity.Error);
                return;
            }
            if (userDto.Username is null)
            {
                Snackbar.Add("The user model returned by the dialog did not have a username set.");
                return;
            }
            if (userDto.Password is null)
            {
                Snackbar.Add("The user model returned by the dialog did not have a password.", Severity.Error);
                return;
            }

            var appUser = UserDto.ToApplicationUser(userDto);
            if (appUser.Email is null)
            {
                Snackbar.Add("The application user converted from the DTO did not have an email");
                return;
            }

            ApplicationUser? existing = await UserManager.FindByEmailAsync(appUser.Email);
            if (existing is not null)
            {
                Snackbar.Add("A user with this email already exists!", Severity.Warning);
                return;
            }

            appUser.EmailConfirmed = true; // Bypass email confirmation

            IdentityResult result = await UserManager.CreateAsync(appUser, userDto.Password);
            if (result.Succeeded)
            {

                IdentityResult roleResult = await UserManager.AddToRolesAsync(appUser, userDto.Roles);
                if (!roleResult.Succeeded)
                {
                    Snackbar.Add("Failed to set roles on user!", Severity.Error);
                    foreach (IdentityError error in roleResult.Errors)
                    {
                        Snackbar.Add(error.Description, Severity.Error);
                    }
                } 

                Snackbar.Add("User created!", Severity.Success);
                await DataGrid.ReloadServerData();
                StateHasChanged();
                return;
            } 
            else
            {
                Snackbar.Add($"Failed to create user with errors, check logs. {result.Errors.ToString()}");
                return;
            }
        }
    }

    private async Task OnEditClicked(UserDto row)
    {

    }

    private async Task OnDeleteClicked(UserDto row)
    {
        // Check if we are trying to delete the last Admin user
        // solution: https://stackoverflow.com/questions/20449008/count-the-number-of-users-in-a-role-in-aspnet-identity-not-membership
        var context = await ContextFactory.CreateDbContextAsync();
        IdentityRole adminRole = await context.Roles.FirstAsync(r => r.Name == UserRoles.Admin);
        Int32 countAdminUsers = await context.Set<ApplicationUser>().CountAsync();

        if (countAdminUsers == 1)
        {
            Snackbar.Add("Cannot delete the only admin user!", Severity.Error);
            return;
        }

        bool? confirmed = await DialogService.ShowMessageBox
        (
            new MessageBoxOptions(){
            Message=$"Are you sure you want to delete this user?", 
            Title="Delete User?", 
            CancelText="Cancel", 
            YesText="Delete"
        });

        if (confirmed is not null)
        {
            // Grab the user from the database, ASP.NET identity will only allow you to 
            // delete the exact object from the database
            if (row.Id is null)
            {
                Snackbar.Add("The user row ID was null, cannot delete", Severity.Error);
                return;
            }
            ApplicationUser? appUser = await UserManager.FindByIdAsync(row.Id);
            if (appUser is null)
            {
                Snackbar.Add("User no longer in database, cannot find by ID.", Severity.Error);
                return;
            }

            IdentityResult result = await UserManager.DeleteAsync(appUser);
            if (result.Succeeded)
            {
                await DataGrid.ReloadServerData();
                StateHasChanged();
                Snackbar.Add("User deleted!", Severity.Success);
            } else
            {
                Snackbar.Add("Call to DeleteAsync failed.", Severity.Error);
                foreach (var error in result.Errors)
                {
                    Snackbar.Add(error.Description, Severity.Error);
                }
            }
        }
    }

    private async Task<GridData<UserDto>> ServerReload(GridState<UserDto> state)
    {
        var context = await ContextFactory.CreateDbContextAsync();

        int totalItems = await context.Users.CountAsync();


        IEnumerable<UserDto> users = await context.Users
        .Skip(state.Page * state.PageSize)
        .Take(state.PageSize)
        .Select(user=>new UserDto
        {
            Id = user.Id,
            Username = user.UserName,
            Roles = (from userRole in context.UserRoles
            join role in context.Roles on userRole.RoleId equals role.Id
            where userRole.UserId == user.Id
            select role.Name).ToList()
        }).ToListAsync();

        return new GridData<UserDto>()
            {
                TotalItems = totalItems,
                Items = users 
            };
    }

    public async Task<List<ApplicationUser>> SelectedItems()
    {
        List<ApplicationUser> selectedUsers = [];
        foreach (UserDto row in DataGrid.SelectedItems)
        {
            await using var context = await ContextFactory.CreateDbContextAsync();

            var user = await context.Users.Where(user => user.Id == row.Id).FirstAsync();
            if (user is null)
            {
                Snackbar.Add($"User with id {row.Id} no longer exists in the database.", Severity.Error);
                continue;
            }
            selectedUsers.Add(user);
        }

        return selectedUsers;
    }

    public async Task SetSelectedItemsAsync(IEnumerable<ApplicationUser> postsToSelect)
    {
        var context = await ContextFactory.CreateDbContextAsync();

        var rowElementsToSelect = postsToSelect.Select(user=> new UserDto
        {
            Id = user.Id,
            Username = user.UserName,
            Roles = (from userRole in context.UserRoles
            join role in context.Roles on userRole.RoleId equals role.Id
            select role.Name).ToList()
        });

        _selectedItems = new HashSet<UserDto>(rowElementsToSelect, _comparer);

        _selectedItems = new HashSet<UserDto>();
        StateHasChanged();
    }

    public void ClearSelected()
    {
        _selectedItems = new HashSet<UserDto>(_comparer);
    }
    
}
