@page "/admin/roles"
@page "/admin/roles/{DatabaseStr}"
@page "/admin/roles/{DatabaseStr}/{DatabaseInstanceStr}"
@using Rms.Risk.Mango.Pivot.Core.MongoDb
@attribute [Authorize]

@inject NavigationManager      NavigationManager
@inject IUserSession            UserSession
@inject IJSRuntime             JsRuntime
@inject IMongoDbServiceFactory MongoDbServiceFactory;

@*
 *                                dbMango
 *
 * Copyright 2025 Deutsche Bank AG
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *@

<style>
    .panel{
        height: calc(100vh - 90px);
    }

    .panel .form-control {
        height: 100%;
    }

    .panel .list-body {
        height: calc(100vh - 150px - 14px);
        max-height: calc(100vh - 150px - 14px);
        overflow-y: auto;
    }

    .builtin {
        color: darkgrey;
    }
    
</style>

<h3 class="mt-3">Roles Management</h3>

<AuthorizedOnly Policy="AdminAccess" Resource="@UserSession.Database">
    @if (Error != null)
    {
        <ExceptionControl Exception="@Error"/>
    }

    <SplitPanel Orientation="horizontal" InitialSplit="0.2">
        <First>
            <div class="panel">
                <div class="mr-3">
                    <div class="form-row mb-3 ml-0">
                        <FormButton Enabled="@IsReady" Name="Refresh" Icon="icon-reload-sm" OnClick="LoadRoles" IsPrimary="false" />
                        <FormButton Enabled="@IsReady" Name="Add" Icon="icon-plus-sm" OnClick="AddRole" IsPrimary="false" />
                    </div>


                    <FormItemList Enabled="@IsReady" Class="mr-2" Name="Roles" @bind-Value="SelectedRole" Values="@RolesList" IsSelectable="@IsSelectable">
                        <OptionTemplate>
                            <div class="d-flex">
                                <div class="flex-grow-1 m-1 @GetRoleClass(context)">@context.RoleName</div>
                                @if (context.IsBuiltin)
                                {
                                    <div class="m-1 builtin">BuiltIn</div>
                                }
                                else if (!string.IsNullOrWhiteSpace(context.Db))
                                {
                                    <div class="m-1">@context.Db</div>
                                }
                            </div>
                        </OptionTemplate>
                    </FormItemList>
                </div>
            </div>
        </First>
        <Second>
            <div class="panel">
                <div class="ml-3">
                    <div class="form-row fit-content mb-3">
                        <FormButton Name="Save"   OnClick="UpdateRole" Icon="icon-document-upload-sm" Enabled="@CanUpdate"/>
                        <FormButton Name="Delete" OnClick="DeleteRole" Icon="icon-trash-sm"           Enabled="@CanDelete" />
                        <FormButton Name=""       OnClick="CopyRole"   Icon="icon-duplicate-document" />
                        <FormButton Name=""       OnClick="PasteRole"  Icon="icon-paste" />
                    </div>

                    <RoleEditControl
                        IsEnabled="@(IsReady && !EditableSelectedRole.IsBuiltin && !string.IsNullOrWhiteSpace( EditableSelectedRole.Db ))"
                        Value="@EditableSelectedRole"
                        AllRoles="@AllRoles"
                        AllActions="@AllActions"
                        AllDatabases="@AllDatabases"
                        IsNew="@IsNew"
                        />
                </div>
            </div>
        </Second>
    </SplitPanel>


</AuthorizedOnly>

@code {
    [CascadingParameter] public IModalService Modal { get; set; } = null!;

    [Parameter] public string? DatabaseStr         { get; set; }
    [Parameter] public string? DatabaseInstanceStr { get; set; }

    private string Database
    {
        get => UserSession.Database;
        set
        {
            if (UserSession.Database == value)
                return;
            UserSession.Database = value;
            SyncUrl();
        }
    }

    private string DatabaseInstance
    {
        get => UserSession.DatabaseInstance;
        set
        {
            if (UserSession.DatabaseInstance == value)
                return;
            UserSession.DatabaseInstance = value;
            SyncUrl();
        }
    }


    private Exception?     Error   { get; set; }
    private bool           IsReady { get; set; }

    private RoleInfoModel       SelectedRole
    {
        get;
        set
        {
            if ( field == value )
                return;
            field = value;

            // Populate AllRoles with non-built-in roles from the same database and built-in roles paired with it.
            AllRoles = RolesList.Where(x => !x.IsBuiltin && x.Db == SelectedRole.Db)
                   .Select(x => new RoleInDbModel
                        { 
                            Role = x.RoleName, 
                            Db   = x.Db 
                        }
                    )
                   .Concat( 
                        RolesList
                           .Where(x => x.IsBuiltin)
                           .Select(x => new RoleInDbModel
                            {
                                Role = x.RoleName,
                                Db   = ""
                            })
                    )
                   .DistinctBy(x => $"{x.Db}, {x.Role}")
                   .OrderBy(x => $"{x.Db}, {x.Role}")
                   .ToList()
                ;

            EditableSelectedRole = field.Clone();
        }
    } = new();

    private RoleInfoModel       EditableSelectedRole { get; set; } = new();
    private List<RoleInfoModel> RolesList            { get; set; } = [];
    private bool                IsNew                => RolesList.All(x => x.RoleName != EditableSelectedRole.RoleName);
    private List<string>        AllDatabases         { get; set; } = [];
    private List<RoleInDbModel> AllRoles             { get; set; } = []; 
    private List<string>        AllActions           { get; set; } = [];
    private bool                CanUpdate            => !EditableSelectedRole.IsBuiltin && !string.IsNullOrWhiteSpace(EditableSelectedRole.Db);
    private bool                CanDelete            => !EditableSelectedRole.IsBuiltin && !string.IsNullOrWhiteSpace(EditableSelectedRole.Db) && !IsNew;

    private bool IsSelectable(RoleInfoModel arg) => true;

    protected override void OnAfterRender(bool firstRender)
    {
        if (!firstRender)
            return;

        if (string.IsNullOrWhiteSpace(DatabaseStr))
            DatabaseStr = Database;
        else
            Database = DatabaseStr;

        if (string.IsNullOrWhiteSpace(DatabaseInstanceStr))
            DatabaseInstanceStr = DatabaseInstance;
        else
            DatabaseInstance = DatabaseInstanceStr;

        SyncUrl();
        StateHasChanged();
        Task.Run(LoadRoles);
    }

    private Task<string?> CanExecuteCommand()
        => Shell.CanExecuteCommand(UserSession, InvokeAsync, Modal);

    private async Task Run(Func<string, CancellationToken,Task> body)
    {
        var ticket = await CanExecuteCommand();
        if (string.IsNullOrWhiteSpace(ticket))
            return;

        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

        try
        {
            IsReady = false;
            await InvokeAsync(StateHasChanged);

            await body(ticket, cts.Token);
        }
        catch (Exception e)
        {
            await Display(e);
        }
        finally
        {
            IsReady = true;
            await InvokeAsync(StateHasChanged);
        }
    }

    private Task LoadRoles()
    => Run(async (_, token) =>
    {
        try
        {
            AllDatabases = (await UserSession.MongoDbAdminForAdminDatabase.ListDatabases(token))
                .Select(x => x.Name)
                .ToList();
        }
        catch (Exception)
        {
            AllDatabases = [UserSession.DatabaseInstance];
        }

        var adminTask   = UserSession.MongoDbAdminForAdminDatabase.GetRolesInfo(showBuiltInRoles: true, token: token);
        var customTasks = AllDatabases.Select(x => LoadRolesForDatabaseInstance(x, token)).ToList();

        await Task.WhenAll(customTasks.Concat([adminTask]));  

        var admin = await adminTask;
        var all = new RolesInfoModel();
        all.Roles.AddRange(admin.Roles.Where(x => x.IsBuiltin));

        foreach (var customTask in customTasks)
        {
            var custom = await customTask;
            all.Roles.AddRange(custom.Roles);
        }
        await Display(all);
    });

    private Task<RolesInfoModel> LoadRolesForDatabaseInstance(string instanceName, CancellationToken token)
    {
        try
        {
            var db = MongoDbServiceFactory.CreateAdmin(UserSession.Database, UserSession, instanceName);
            return db.GetRolesInfo(showBuiltInRoles: false, token: token);
        }
        catch (Exception)
        {
            return Task.FromResult(new RolesInfoModel() );
        }
    }


    private Task Display(RolesInfoModel model)
    {
        Error  = null;

        var oldRoleName = SelectedRole.RoleName;

        foreach (var role in model.Roles.Where(role => role.IsBuiltin || string.IsNullOrWhiteSpace(role.Db)))
        {
            role.Db = "";
        }

        RolesList = model.Roles
            .OrderBy(x => $"{x.Db}, {x.RoleName}")
            .ToList()
            ;

        SelectedRole = !string.IsNullOrWhiteSpace(oldRoleName) 
                ? model.Roles.FirstOrDefault(x => x.RoleName == oldRoleName) ?? model.Roles.FirstOrDefault() ?? new() 
                : model.Roles.FirstOrDefault() ?? new RoleInfoModel()
            ;

        AllActions = RolesList
               .SelectMany(x => x.Privileges.SelectMany(y => y.Actions))
               .Distinct()
               .OrderBy(x => x)
               .ToList()
            ;

        return InvokeAsync(StateHasChanged);
    }


    private Task Display(Exception e)
    {
        Error = e;
        RolesList.Clear();
        AllRoles.Clear();
        AllActions.Clear();
        AddRole();

        return InvokeAsync(StateHasChanged);
    }

    private void SyncUrl()
    {
        var url = NavigationManager.BaseUri + $"admin/roles/{Database}"; 
        if (!string.IsNullOrWhiteSpace(DatabaseInstance))
            url += $"/{DatabaseInstance}";
        JsRuntime.InvokeAsync<string>("DashboardUtils.ChangeUrl", url);
    }

    private static string GetRoleClass(RoleInfoModel r) => r.IsBuiltin ? "builtin" : "";

    private static int _count;

    private Task AddRole()
    {
        SelectedRole = new ()
        {
            RoleName = $"newRole{++_count}",
            Db       = UserSession.DatabaseInstance,
            IsBuiltin = false
        };

        return InvokeAsync(StateHasChanged);
    }

    private async Task DeleteRole()
    {
        if ( IsNew || string.IsNullOrWhiteSpace(SelectedRole.RoleName) || SelectedRole.IsBuiltin )
        {
            await ModalDialogUtils.ShowInfoDialog( Modal, "Oops!", "Cannot delete the selected role.");
            return;
        }

        var rc = await ModalDialogUtils.ShowConfirmationDialog(Modal, "Confirm Deletion", $"Are you sure you want to delete the role '{SelectedRole.RoleName}'?");
        if (!rc.Confirmed)
            return;

        // Logic for deleting the selected role goes here
        await Run(async (_, token) =>
        {
            // Attention: use the admin service corresponding to the database you are going to delete role in!
            var service = MongoDbServiceFactory.CreateAdmin(UserSession.Database, UserSession, SelectedRole.Db);
            await service.DropRole(SelectedRole.RoleName, token);
            await LoadRoles();
        });
    }

    private async Task UpdateRole()
    {
        var role = EditableSelectedRole;
        var add = IsNew;

        if (role.IsBuiltin || string.IsNullOrWhiteSpace(role.Db))
        {
            await ModalDialogUtils.ShowInfoDialog(Modal, "Oops!", $"Can't delete built-in role '{role.RoleName}'.");
            return;
        }


        if (add && RolesList.Any(x => x.RoleName == role.RoleName && x.Db == role.Db ))
        {
            await ModalDialogUtils.ShowInfoDialog(Modal, "Oops!", $"Role '{role.RoleName}' already exists. Please choose a different name.");
            return;
        }

        var rc = await ModalDialogUtils.ShowConfirmationDialog(Modal, "Warning", $"Are you sure you want to {(add ? "add" : "update")} the role '{role.RoleName}'?");
        if (!rc.Confirmed)
            return;

        var command = role.CreateUpdateRoleCommand(add);

        // var json = command.ToJson( new () { Indent = true });
        // await ModalDialogUtils.ShowTextDialog(Modal, "Oops!", json, $"Role '{role.RoleName}'");


        // Proceed with updating or adding the role logic here
        await Run(async (_, token) =>
        {
            // Attention: use the admin service corresponding to the database you are going to create role in!
            var service = MongoDbServiceFactory.CreateAdmin(UserSession.Database, UserSession, role.Db);
            await service.RunCommand(command, token);
            await LoadRoles();
        });
    }

    private async Task CopyRole()
    {
        try
        {
            var json = JsonUtils.ToJson(SelectedRole, new() { WriteIndented = true });
            await JsRuntime.InvokeVoidAsync("DashboardUtils.CopyToClipboard", json);
            await ModalDialogUtils.ShowInfoDialog(Modal, "Copied", "Role copied to clipboard.");
        }
        catch (Exception ex)
        {
            await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex);
        }

    }

    private async Task PasteRole()
    {
        try
        {
            var json = await JsRuntime.InvokeAsync<string>("DashboardUtils.PasteFromClipboard");

            var clone = JsonUtils.FromJson<RoleInfoModel>(json);
            if (clone == null)
                return;

            // Search for the existing role by name
            var existingRole = RolesList.FirstOrDefault(x => x.RoleName == clone.RoleName);
            if ( existingRole != null )
            {
                if ( existingRole.IsBuiltin )
                {
                    // If the role is built-in, show a warning and do not overwrite
                    await ModalDialogUtils.ShowInfoDialog(Modal, "Oops!", $"Role '{clone.RoleName}' is a built-in role and cannot be changed.");
                    return;
                }

                // If the role already exists, show a warning and do not overwrite
                var rc = await ModalDialogUtils.ShowConfirmationDialog(Modal, "Warning", $"Role '{clone.RoleName}' already exists. Please choose a different name.");
                if (!rc.Confirmed)
                    return;
            }

            SelectedRole = clone;

            await InvokeAsync(StateHasChanged);
            await ModalDialogUtils.ShowInfoDialog(Modal, "Copied", $"Role {SelectedRole.RoleName} was successfully parsed. You still need to save it!");
        }
        catch (Exception ex)
        {
            await ModalDialogUtils.ShowExceptionDialog(Modal, "Error", ex);
        }    
    }

}
