using System.Data;

public class AuthCache
{
    private static List<AppUser> AppUsers = new();
    private static List<SecurityGroup> SecurityGroups = new();
    private static ILookup<int, SecurityGroup> SecurityGroupsByAppUserId = Enumerable.Empty<SecurityGroup>().ToLookup(x => 0);
    private static HashSet<string> SecurtionActionNames = new();
    private static DateTime LastCacheRefresh = DateTime.MinValue;
    private static SemaphoreSlim CacheRefreshSemaphore = new SemaphoreSlim(1, 1);
    private static TimeSpan RefreshInterval { get; set; }

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

    public async Task<List<AppUser>> GetAppUsers()
    {
        await WaitOrRefreshAll();
        return AppUsers;
    }

    public async Task<List<SecurityGroup>> GetSecurityGroups()
    {
        await WaitOrRefreshAll();
        return SecurityGroups;
    }

    public async Task<ILookup<int, SecurityGroup>> GetSecurityGroupsByAppUserId()
    {
        await WaitOrRefreshAll();
        return SecurityGroupsByAppUserId;
    }

    public async Task<HashSet<string>> GetSecurityActionNames()
    {
        await WaitOrRefreshAll();
        return SecurtionActionNames;
    }

    private async Task WaitOrRefreshAll()
    {
        if (DateTime.UtcNow - LastCacheRefresh > RefreshInterval)
            await RefreshAll();

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

    private async Task RefreshAll()
    {
        if (await CacheRefreshSemaphore.WaitAsync(0))
        {
            try
            {
                Parallel.Invoke(
                    () => RefreshAppUsers(),
                    () => RefreshSecurityGroups(),
                    () => RefreshSecurityGroupsByAppUserId(),
                    () => RefreshSecurityActionNames()
                );
            }
            finally
            {
                LastCacheRefresh = DateTime.UtcNow;
                CacheRefreshSemaphore.Release();
            }
        }
    }

    private void RefreshAppUsers()
    {
        using var db = Main.CreateContext();
        var newData = (
            from q in db.AppUsers
            select q
        ).AsNoTracking().ToList();
        AppUsers = newData;
    }

    private void RefreshSecurityGroups()
    {
        using var db = Main.CreateContext();
        var newData = (
            from q in db.SecurityGroups
                .Include(x => x.SecurityUserGroups)
                .Include(x => x.SecurityGroupPermissions)
                .ThenInclude(x => x.SecurityAction)
            select q
        ).AsNoTracking().ToList();
        SecurityGroups = newData;
    }

    private void RefreshSecurityGroupsByAppUserId()
    {
        using var db = Main.CreateContext();
        var newData = (
            from q in db.SecurityUserGroups
                .Include(x => x.SecurityGroup)
                .ThenInclude(x => x.SecurityGroupPermissions)
                .ThenInclude(x => x.SecurityAction)
            select new { q.UserId, q.SecurityGroup }
        ).AsNoTracking().ToLookup(x => x.UserId, x => x.SecurityGroup);
        SecurityGroupsByAppUserId = newData;
    }

    private void RefreshSecurityActionNames()
    {
        using var db = Main.CreateContext();
        var newData = (
            from q in db.SecurityActions
            select q.Name
        ).AsNoTracking().ToHashSet(StringComparer.OrdinalIgnoreCase);
        SecurtionActionNames = newData;
    }
}
