using System.Data;
using System.Security.Claims;

namespace Fast.Logic;

public class ScreenItem : Screen
{
    public bool UserHasPermission { get; set; }
}


public class ScreenCache
{
    private static List<ScreenItem> Screens = [];
    private static DateTime LastCacheRefresh = DateTime.MinValue;
    private static readonly SemaphoreSlim CacheRefreshSemaphore = new(1, 1);
    private static TimeSpan RefreshInterval { get; set; }

    public ScreenCache(TimeSpan refreshInterval)
    {
        RefreshInterval = refreshInterval;
    }

    public async Task<List<ScreenItem>> GetScreens(ClaimsPrincipal user)
    {
        if (DateTime.UtcNow - LastCacheRefresh > RefreshInterval)
            await RefreshScreensAsync();

        while (LastCacheRefresh == DateTime.MinValue)
            await Task.Delay(100);

        var screenWithPermissionsUpdated = await GetScreenWithPermissionsUpdated(Screens, user);
        return screenWithPermissionsUpdated;
    }

    private static async Task<List<ScreenItem>> GetScreenWithPermissionsUpdated(List<ScreenItem> screens, ClaimsPrincipal user)
    {
        List<ScreenItem> screensWithPermissions = [.. screens];
        var authHelper = new AuthorizationHelper(Main.IsAuthenticationEnabled);

        //set screens permissions
        foreach (var screen in screensWithPermissions)
        {
            var isModule = screen.Description?.ToLower().Contains("module") ?? false;
            if (screen.ParentId != null && !isModule)
            {
                var isViewAction = screen.SecurityAction?.IsView ?? false;
                var permissionType = isViewAction ? PermissionType.View : PermissionType.Standard;
                var actionName = screen.SecurityAction?.Name ?? $"missing action for {screen.Name}";
                //if the screen is not visible, it should not have any security action since the action is only used to determine dash/menu visibiilty
                var hasPermission = screen.IsVisible && await authHelper.IsAuthorizedAsync(user, actionName, permissionType);
                if (hasPermission)
                    screen.UserHasPermission = true;
            }
        }

        //remove modules where the user doesnt have permission
        var modulesToRemove = new List<string>();
        foreach (var screen in screensWithPermissions)
        {
            var isModule = screen.Description?.ToLower().Contains("module") ?? false;
            if (isModule)
            {
                screen.UserHasPermission = true;
                var hasAnyChildren = screensWithPermissions.Any(x => x.ParentId == screen.Id && x.UserHasPermission);
                if (!hasAnyChildren && screen.Name != null)
                    modulesToRemove.Add(screen.Name);
            }
        }

        screensWithPermissions.RemoveAll(x => x.Name != null && modulesToRemove.Contains(x.Name));
        return screensWithPermissions;
    }

    private static async Task RefreshScreensAsync()
    {
        if (await CacheRefreshSemaphore.WaitAsync(0))
        {
            try
            {
                using var db = Main.CreateContext();
                var newData = (
                    from q in db.Screens
                        .Include(x => x.SecurityAction)
                    orderby q.Name
                    select new ScreenItem
                    {
                        Id = q.Id,
                        Name = q.Name,
                        Description = q.Description,
                        Keywords = q.Keywords,
                        Path = q.Path,
                        IsVisible = q.IsVisible,
                        ParentId = q.ParentId,
                        SecurityActionId = q.SecurityActionId,
                        SecurityAction = q.SecurityAction,
                        InverseParent = q.InverseParent,
                        Parent = q.Parent,
                        UserHasPermission = false
                    }
                ).AsNoTracking().ToList();

                Screens = newData;
            }
            finally
            {
                LastCacheRefresh = DateTime.UtcNow;
                CacheRefreshSemaphore.Release();
            }
        }
    }
}
