using LSFE.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;

namespace LSFE.Domain.Services.Security
{
    public interface IRoleAuthorizationCache
    {
        Task<List<string>> GetAllowedRoleIdsAsync(string controller);
        Task<bool> UserHasAccessAsync(string userId, string controller);
        Task<List<string>> GetUserRoleIdsAsync(string userId);
        void InvalidateCache(string controller);
        void InvalidateUserCache(string userId);
        void InvalidateAllCache();
    }

    public class RoleAuthorizationCache : IRoleAuthorizationCache
    {
        private readonly IMemoryCache _cache;
        private readonly LSFEDbContext _dbContext;
        private readonly ILogger<RoleAuthorizationCache> _logger;

        private const string CONTROLLER_ROLES_PREFIX = "controller_roles_";
        private const string USER_ROLES_PREFIX = "user_roles_";
        private const int CACHE_DURATION_MINUTES = 30;

        public RoleAuthorizationCache(
            IMemoryCache cache,
            LSFEDbContext dbContext,
            ILogger<RoleAuthorizationCache> logger)
        {
            _cache = cache ?? throw new ArgumentNullException(nameof(cache));
            _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        /// <summary>
        /// Get all role IDs that have access to a specific controller (CACHED)
        /// </summary>
        public async Task<List<string>> GetAllowedRoleIdsAsync(string controller)
        {
            if (string.IsNullOrWhiteSpace(controller))
                throw new ArgumentException("Controller name cannot be null or empty", nameof(controller));

            var cacheKey = $"{CONTROLLER_ROLES_PREFIX}{controller}";

            return await _cache.GetOrCreateAsync(cacheKey, async entry =>
            {
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CACHE_DURATION_MINUTES);
                entry.SetPriority(CacheItemPriority.High);

                _logger.LogInformation("Cache MISS: Loading role permissions for controller: {Controller}", controller);

                var roleIds = await _dbContext.AuthorizeRoles
                    .Where(ar => ar.IsActive && ar.Controller == controller)
                    .Select(ar => ar.RoleId)
                    .Distinct()
                    .ToListAsync();

                _logger.LogInformation("Cached {Count} roles for controller: {Controller}", roleIds.Count, controller);

                return roleIds ?? new List<string>();
            });
        }

        /// <summary>
        /// Get all role IDs for a specific user (CACHED)
        /// </summary>
        public async Task<List<string>> GetUserRoleIdsAsync(string userId)
        {
            if (string.IsNullOrWhiteSpace(userId))
                throw new ArgumentException("User ID cannot be null or empty", nameof(userId));

            var cacheKey = $"{USER_ROLES_PREFIX}{userId}";

            return await _cache.GetOrCreateAsync(cacheKey, async entry =>
            {
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CACHE_DURATION_MINUTES);
                entry.SetPriority(CacheItemPriority.High);

                _logger.LogInformation("Cache MISS: Loading roles for user: {UserId}", userId);

                var userRoleIds = await _dbContext.UserRoles
                    .Where(ur => ur.UserId == userId)
                    .Select(ur => ur.RoleId)
                    .ToListAsync();

                _logger.LogInformation("Cached {Count} roles for user: {UserId}", userRoleIds.Count, userId);

                return userRoleIds ?? new List<string>();
            });
        }

        /// <summary>
        /// Check if a specific user has access to a controller (FULLY CACHED)
        /// </summary>
        public async Task<bool> UserHasAccessAsync(string userId, string controller)
        {
            if (string.IsNullOrWhiteSpace(userId))
                throw new ArgumentException("User ID cannot be null or empty", nameof(userId));

            if (string.IsNullOrWhiteSpace(controller))
                throw new ArgumentException("Controller name cannot be null or empty", nameof(controller));

            try
            {
                // Get allowed role IDs from cache (or database if cache miss)
                var allowedRoleIds = await GetAllowedRoleIdsAsync(controller);

                if (!allowedRoleIds.Any())
                {
                    _logger.LogWarning("No roles configured for controller: {Controller}", controller);
                    return false;
                }

                // Get user's role IDs from cache (or database if cache miss)
                var userRoleIds = await GetUserRoleIdsAsync(userId);

                if (!userRoleIds.Any())
                {
                    _logger.LogWarning("User {UserId} has no roles assigned", userId);
                    return false;
                }

                // Check if user has any of the required roles (IN-MEMORY operation, no database!)
                var hasAccess = userRoleIds.Any(userRole => allowedRoleIds.Contains(userRole));

                _logger.LogDebug("User {UserId} access to {Controller}: {HasAccess}", userId, controller, hasAccess);

                return hasAccess;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error checking access for user {UserId} on controller {Controller}",
                    userId, controller);
                throw;
            }
        }

        /// <summary>
        /// Invalidate cache for a specific controller
        /// </summary>
        public void InvalidateCache(string controller)
        {
            if (string.IsNullOrWhiteSpace(controller))
                return;

            var cacheKey = $"{CONTROLLER_ROLES_PREFIX}{controller}";
            _cache.Remove(cacheKey);

            _logger.LogInformation("Cache invalidated for controller: {Controller}", controller);
        }

        /// <summary>
        /// Invalidate cache for a specific user (call this when user roles change)
        /// </summary>
        public void InvalidateUserCache(string userId)
        {
            if (string.IsNullOrWhiteSpace(userId))
                return;

            var cacheKey = $"{USER_ROLES_PREFIX}{userId}";
            _cache.Remove(cacheKey);

            _logger.LogInformation("Cache invalidated for user: {UserId}", userId);
        }

        /// <summary>
        /// Invalidate all controller role caches
        /// </summary>
        public void InvalidateAllCache()
        {
            _logger.LogWarning("All cache invalidation requested - consider implementing a cache key tracking system");
        }
    }
}