using System.Security.Claims;

namespace Fast.Shared.Logic;

public class AuthorizationHelper
{
    //this helper class caches and reuses data
    //we don't use IsAuthorized in Authorization.cs of the main web project because
    //over there we want to re-check the database every time

    private Dictionary<int, AppUser>? _appUsersDic;
    private Dictionary<int, Dictionary<string, SecurityGroupPermission>> _permissionsByGroupIdThenByActionName = new();
    private readonly bool isAuthenticationEnabled;

    public AuthorizationHelper(bool isAuthenticationEnabled)
    {
        this.isAuthenticationEnabled = isAuthenticationEnabled;
    }

    public async Task<bool> IsAuthorizedAsync(int userInfoId, string securityActionName, PermissionType permission)
    {
        if (isAuthenticationEnabled)
        {
            bool isAllowed = false;
            string actionName = securityActionName.ToLower();
            bool actionExists = (await Main.AuthCache.GetSecurityActionNames()).Contains(actionName);

            if (!actionExists)
                throw new Exception($"The security action [{securityActionName}] was not found.");

            var secGroups = (await Main.AuthCache.GetSecurityGroupsByAppUserId())[userInfoId].ToList();

            int groupIdx = 0;
            while (!isAllowed && groupIdx < secGroups.Count)
            {
                var secGroup = secGroups[groupIdx];
                var groupPermissionsByActionName = (await GetPermissionsByGroupIdThenByActionName())[secGroup.Id];

                bool isGroupDenied = GetIsGroupDenied(groupPermissionsByActionName, permission, actionName);
                if (!isGroupDenied)
                    isAllowed = GetIsGroupAllowed(groupPermissionsByActionName, permission, actionName);

                groupIdx++;
            }

            return isAllowed;
        }
        else
            return true;
    }

    public async Task<bool> IsAuthorizedAsync(ClaimsPrincipal user, string securityActionName, PermissionType permission)
    {
        if (isAuthenticationEnabled)
        {
            var appUserId = Util.GetAppUserId(user);
            return await IsAuthorizedAsync(appUserId, securityActionName, permission);
        }
        else
            return true;
    }

    public async Task<List<int>> GetAllUserInfoIdsForPermissionAsync(string securityActionName, PermissionType permission)
    {
        HashSet<int> userInfoIds = new();

        foreach (var secGroup in (await Main.AuthCache.GetSecurityGroups()))
        {
            var groupPermissionsByActionName = (await GetPermissionsByGroupIdThenByActionName())[secGroup.Id];

            bool isGroupAllowed = false;
            bool isGroupDenied = GetIsGroupDenied(groupPermissionsByActionName, permission, securityActionName);
            if (!isGroupDenied)
                isGroupAllowed = GetIsGroupAllowed(groupPermissionsByActionName, permission, securityActionName);

            if (isGroupAllowed)
            {
                foreach (var userGroup in secGroup.SecurityUserGroups)
                {
                    if (!userInfoIds.Contains(userGroup.UserId))
                        userInfoIds.Add(userGroup.UserId);
                }
            }
        }

        return userInfoIds.ToList();
    }

    public bool GetIsGroupDenied(Dictionary<string, SecurityGroupPermission> groupPermissionsByActionName, PermissionType permissionType, string actionName)
    {
        if (isAuthenticationEnabled)
        {
            bool isGroupDenied = true;

            bool isAllActionsDenied = false;
            bool isViewAllDenied = false;
            bool isModifyAllDenied = false;
            bool isActionDenied = false;

            if (groupPermissionsByActionName.ContainsKey("all actions"))
                isAllActionsDenied = !groupPermissionsByActionName["all actions"].IsAllowed;

            if (groupPermissionsByActionName.ContainsKey("view all"))
                isViewAllDenied = !groupPermissionsByActionName["view all"].IsAllowed;

            if (groupPermissionsByActionName.ContainsKey("modify all"))
                isModifyAllDenied = !groupPermissionsByActionName["modify all"].IsAllowed;

            if (groupPermissionsByActionName.ContainsKey(actionName))
            {
                if (permissionType == PermissionType.View)
                    isActionDenied = !groupPermissionsByActionName[actionName].IsViewAllowed;
                else if (permissionType == PermissionType.Modify)
                    isActionDenied = !groupPermissionsByActionName[actionName].IsModifyAllowed;
                else
                    isActionDenied = !groupPermissionsByActionName[actionName].IsAllowed;
            }

            if (permissionType == PermissionType.View)
                isGroupDenied = isAllActionsDenied || isViewAllDenied || isActionDenied;
            else if (permissionType == PermissionType.Modify)
                isGroupDenied = isAllActionsDenied || isModifyAllDenied || isActionDenied;
            else if (permissionType == PermissionType.Standard)
                isGroupDenied = isAllActionsDenied || isActionDenied;

            return isGroupDenied;
        }
        else
            return false;
    }

    public bool GetIsGroupAllowed(Dictionary<string, SecurityGroupPermission> groupPermissionsByActionName, PermissionType permissionType, string actionName)
    {
        if (isAuthenticationEnabled)
        {
            bool isGroupAllowed = true;

            bool isAllActionsAllowed = false;
            bool isViewAllAllowed = false;
            bool isModifyAllAllowed = false;
            bool isActionAllowed = false;

            if (groupPermissionsByActionName.ContainsKey("all actions"))
                isAllActionsAllowed = groupPermissionsByActionName["all actions"].IsAllowed;

            if (groupPermissionsByActionName.ContainsKey("view all"))
                isViewAllAllowed = groupPermissionsByActionName["view all"].IsAllowed;

            if (groupPermissionsByActionName.ContainsKey("modify all"))
                isModifyAllAllowed = groupPermissionsByActionName["modify all"].IsAllowed;

            if (groupPermissionsByActionName.ContainsKey(actionName))
            {
                if (permissionType == PermissionType.View)
                    isActionAllowed = groupPermissionsByActionName[actionName].IsViewAllowed;
                else if (permissionType == PermissionType.Modify)
                    isActionAllowed = groupPermissionsByActionName[actionName].IsModifyAllowed;
                else
                    isActionAllowed = groupPermissionsByActionName[actionName].IsAllowed;
            }

            if (permissionType == PermissionType.View)
                isGroupAllowed = isAllActionsAllowed || isViewAllAllowed || isActionAllowed;
            else if (permissionType == PermissionType.Modify)
                isGroupAllowed = isAllActionsAllowed || isModifyAllAllowed || isActionAllowed;
            else if (permissionType == PermissionType.Standard)
                isGroupAllowed = isAllActionsAllowed || isActionAllowed;

            return isGroupAllowed;
        }
        else
            return true;
    }

    private async Task<Dictionary<int, AppUser>> GetAppUsersDic()
    {
        if (_appUsersDic == null)
            _appUsersDic = (await Main.AuthCache.GetAppUsers()).ToDictionary(x => x.Id);

        return _appUsersDic;
    }

    private async Task<Dictionary<int, Dictionary<string, SecurityGroupPermission>>> GetPermissionsByGroupIdThenByActionName()
    {
        if (!_permissionsByGroupIdThenByActionName.Any())
            _permissionsByGroupIdThenByActionName = (await Main.AuthCache.GetSecurityGroups()).ToDictionary(
                x => x.Id,
                x => x.SecurityGroupPermissions.ToDictionary(x => x.SecurityAction.Name.ToLower())
            );

        return _permissionsByGroupIdThenByActionName;
    }
}
