using Microsoft.Extensions.Caching.Memory;
using OmsorgCore.Application.Abstractions;
using OmsorgCore.Application.Models;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Domain.Enums;

namespace OmsorgCore.Application.Services;

/// <summary>
/// Implementiert die Regel aus REQUIREMENTS.md Abschnitt 3/7 und Blueprint 6.5:
/// Rollen-Default gilt, ein individueller Override (Grant oder Revoke) gewinnt immer.
///
/// Der Rechte-Join (Role→RolePermissions + PermissionOverrides + Employee) ist die teuerste,
/// pro Request wiederholte Query der gesamten API (jeder [RequirePermission]-Endpunkt löst sie
/// aus). Deshalb wird das Ergebnis 60s in einem IMemoryCache gehalten. Der SecurityStamp-Check
/// (Session-Killswitch, siehe Program.cs OnTokenValidated) bleibt bewusst außen vor - der muss
/// laut CLAUDE.md "sofort" wirken, unabhängig von jeder TTL. Die TTL hier ist nur ein
/// Sicherheitsnetz; der Normalfall ist aktive Invalidierung bei jeder Rechte-Mutation
/// (InvalidateUserPermissions/InvalidateRolePermissionsAsync, aufgerufen aus RoleService/UserService).
/// </summary>
public class PermissionService : IPermissionService
{
    private static readonly TimeSpan CacheDuration = TimeSpan.FromSeconds(60);

    private readonly IUserRepository _userRepository;
    private readonly IMemoryCache _cache;

    public PermissionService(IUserRepository userRepository, IMemoryCache cache)
    {
        _userRepository = userRepository;
        _cache = cache;
    }

    public async Task<bool> HasPermissionAsync(Guid userId, ModuleType module, PermissionAction action, CancellationToken cancellationToken = default)
    {
        User? user = await GetCachedUserAsync(userId, cancellationToken);
        if (user is null || !user.IsActive)
        {
            return false;
        }

        return ResolveScope(user, module, action) is not null;
    }

    public async Task<PermissionScope?> GetScopeAsync(Guid userId, ModuleType module, PermissionAction action, CancellationToken cancellationToken = default)
    {
        User? user = await GetCachedUserAsync(userId, cancellationToken);
        if (user is null || !user.IsActive)
        {
            return null;
        }

        return ResolveScope(user, module, action);
    }

    public async Task<IReadOnlyList<PermissionGrant>> GetGrantedPermissionsAsync(Guid userId, CancellationToken cancellationToken = default)
    {
        User? user = await GetCachedUserAsync(userId, cancellationToken);
        if (user is null || !user.IsActive)
        {
            return Array.Empty<PermissionGrant>();
        }

        var grants = new List<PermissionGrant>();
        foreach (ModuleType module in Enum.GetValues<ModuleType>())
        {
            foreach (PermissionAction action in Enum.GetValues<PermissionAction>())
            {
                PermissionScope? scope = ResolveScope(user, module, action);
                if (scope is not null)
                {
                    grants.Add(new PermissionGrant(module, action, scope.Value));
                }
            }
        }

        return grants;
    }

    public void InvalidateUserPermissions(Guid userId)
        => _cache.Remove(CacheKey(userId));

    public async Task InvalidateRolePermissionsAsync(Guid roleId, CancellationToken cancellationToken = default)
    {
        var userIds = await _userRepository.GetUserIdsByRoleAsync(roleId, cancellationToken);
        foreach (var userId in userIds)
        {
            InvalidateUserPermissions(userId);
        }
    }

    private async Task<User?> GetCachedUserAsync(Guid userId, CancellationToken cancellationToken)
    {
        if (_cache.TryGetValue(CacheKey(userId), out User? cached))
        {
            return cached;
        }

        User? user = await _userRepository.GetByIdWithPermissionsNoTrackingAsync(userId, cancellationToken);
        _cache.Set(CacheKey(userId), user, CacheDuration);
        return user;
    }

    private static string CacheKey(Guid userId) => $"user-permissions:{userId}";

    /// <summary>
    /// Löst Modul+Aktion für diesen User zu einem Scope auf, oder <c>null</c> wenn nicht gewährt.
    /// Ein Override ersetzt die Zelle vollständig (Grant+Scope) — es wird nicht mit dem
    /// Rollen-Scope gemergt, analog zur bestehenden Effect-Semantik.
    /// </summary>
    private static PermissionScope? ResolveScope(User user, ModuleType module, PermissionAction action)
    {
        UserPermissionOverride? override_ = user.PermissionOverrides
            .FirstOrDefault(o => o.Module == module && o.Action == action);

        if (override_ is not null)
        {
            return override_.Effect == PermissionEffect.Grant ? override_.Scope : null;
        }

        RolePermission? rolePermission = user.Role.RolePermissions
            .FirstOrDefault(rp => rp.Module == module && rp.Action == action);

        return rolePermission?.Scope;
    }
}
