using Microsoft.Extensions.Caching.Memory;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;

namespace BankSampahApp.Services;

/// <summary>
/// Implementation of cache service dengan enhanced functionality
/// </summary>
public class CacheService : ICacheService
{
    private readonly IMemoryCache _cache;
    private readonly ILogger<CacheService> _logger;
    private readonly ConcurrentDictionary<string, bool> _cacheKeys;

    public CacheService(IMemoryCache cache, ILogger<CacheService> logger)
    {
        _cache = cache ?? throw new ArgumentNullException(nameof(cache));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _cacheKeys = new ConcurrentDictionary<string, bool>();
    }

    /// <inheritdoc/>
    public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> provider, TimeSpan expiration, CacheItemPriority priority = CacheItemPriority.Normal)
    {
        if (_cache.TryGetValue(key, out T? cachedValue))
        {
            _logger.LogDebug("Cache hit for key: {CacheKey}", key);
            return cachedValue!;
        }

        _logger.LogDebug("Cache miss for key: {CacheKey}, fetching fresh data", key);

        var data = await provider();
        Set(key, data, expiration, priority);

        return data;
    }

    /// <inheritdoc/>
    public async Task<T> GetOrSetWithSlidingAsync<T>(string key, Func<Task<T>> provider, TimeSpan slidingExpiration, TimeSpan absoluteExpiration, CacheItemPriority priority = CacheItemPriority.Normal)
    {
        if (_cache.TryGetValue(key, out T? cachedValue))
        {
            _logger.LogDebug("Cache hit for key: {CacheKey}", key);
            return cachedValue!;
        }

        _logger.LogDebug("Cache miss for key: {CacheKey}, fetching fresh data", key);

        var data = await provider();

        var cacheOptions = new MemoryCacheEntryOptions
        {
            SlidingExpiration = slidingExpiration,
            AbsoluteExpirationRelativeToNow = absoluteExpiration,
            Priority = priority,
            PostEvictionCallbacks = { new PostEvictionCallbackRegistration
            {
                EvictionCallback = (key, value, reason, state) =>
                {
                    _cacheKeys.TryRemove(key.ToString()!, out _);
                    _logger.LogDebug("Cache entry evicted: {Key}, Reason: {Reason}", key, reason);
                }
            }}
        };

        _cache.Set(key, data, cacheOptions);
        _cacheKeys.TryAdd(key, true);

        _logger.LogDebug("Cached data with sliding expiration for key: {CacheKey}", key);

        return data;
    }

    /// <inheritdoc/>
    public T? Get<T>(string key)
    {
        if (_cache.TryGetValue(key, out T? value))
        {
            _logger.LogDebug("Retrieved cached value for key: {CacheKey}", key);
            return value;
        }

        _logger.LogDebug("Cache miss for key: {CacheKey}", key);
        return default;
    }

    /// <inheritdoc/>
    public void Set<T>(string key, T value, TimeSpan expiration, CacheItemPriority priority = CacheItemPriority.Normal)
    {
        var cacheOptions = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = expiration,
            Priority = priority,
            PostEvictionCallbacks = { new PostEvictionCallbackRegistration
            {
                EvictionCallback = (key, value, reason, state) =>
                {
                    _cacheKeys.TryRemove(key.ToString()!, out _);
                    _logger.LogDebug("Cache entry evicted: {Key}, Reason: {Reason}", key, reason);
                }
            }}
        };

        _cache.Set(key, value, cacheOptions);
        _cacheKeys.TryAdd(key, true);

        _logger.LogDebug("Cached value for key: {CacheKey}, Expiration: {Expiration}", key, expiration);
    }

    /// <inheritdoc/>
    public void Remove(string key)
    {
        _cache.Remove(key);
        _cacheKeys.TryRemove(key, out _);
        _logger.LogDebug("Removed cache entry for key: {CacheKey}", key);
    }

    /// <inheritdoc/>
    public void RemoveByPattern(string pattern)
    {
        var regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
        var keysToRemove = _cacheKeys.Keys.Where(key => regex.IsMatch(key)).ToList();

        foreach (var key in keysToRemove)
        {
            Remove(key);
        }

        _logger.LogDebug("Removed {Count} cache entries matching pattern: {Pattern}", keysToRemove.Count, pattern);
    }

    /// <inheritdoc/>
    public bool Exists(string key)
    {
        return _cacheKeys.ContainsKey(key);
    }
}