using System.Text.Json;
using Microsoft.Extensions.Logging;
using StackExchange.Redis;

namespace IamService.Infrastructure.Caching;

/// <summary>
/// EN: Redis-based cache service implementation
/// VI: Implementation cache service dựa trên Redis
/// </summary>
public class RedisCacheService : ICacheService
{
    private readonly IConnectionMultiplexer _redis;
    private readonly IDatabase _db;
    private readonly ILogger<RedisCacheService> _logger;
    private readonly JsonSerializerOptions _jsonOptions;
    
    private const string BlacklistPrefix = "blacklist:";
    
    public RedisCacheService(
        IConnectionMultiplexer redis,
        ILogger<RedisCacheService> logger)
    {
        _redis = redis;
        _db = redis.GetDatabase();
        _logger = logger;
        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = false
        };
    }
    
    public async Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default) where T : class
    {
        try
        {
            var value = await _db.StringGetAsync(key);
            if (value.IsNullOrEmpty)
            {
                return null;
            }
            
            return JsonSerializer.Deserialize<T>(value.ToString(), _jsonOptions);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to get value from cache for key: {Key}", key);
            return null;
        }
    }
    
    public async Task<string?> GetStringAsync(string key, CancellationToken cancellationToken = default)
    {
        try
        {
            var value = await _db.StringGetAsync(key);
            return value.IsNullOrEmpty ? null : value.ToString();
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to get string from cache for key: {Key}", key);
            return null;
        }
    }
    
    public async Task SetAsync<T>(string key, T value, TimeSpan? expiration = null, CancellationToken cancellationToken = default) where T : class
    {
        try
        {
            var json = JsonSerializer.Serialize(value, _jsonOptions);
            await _db.StringSetAsync(key, json, expiration);
            _logger.LogDebug("Cached value for key: {Key}, Expiration: {Expiration}", key, expiration);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to set value in cache for key: {Key}", key);
        }
    }
    
    public async Task SetStringAsync(string key, string value, TimeSpan? expiration = null, CancellationToken cancellationToken = default)
    {
        try
        {
            await _db.StringSetAsync(key, value, expiration);
            _logger.LogDebug("Cached string for key: {Key}, Expiration: {Expiration}", key, expiration);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to set string in cache for key: {Key}", key);
        }
    }
    
    public async Task RemoveAsync(string key, CancellationToken cancellationToken = default)
    {
        try
        {
            await _db.KeyDeleteAsync(key);
            _logger.LogDebug("Removed cache for key: {Key}", key);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to remove cache for key: {Key}", key);
        }
    }
    
    public async Task RemoveByPatternAsync(string pattern, CancellationToken cancellationToken = default)
    {
        try
        {
            var endpoints = _redis.GetEndPoints();
            foreach (var endpoint in endpoints)
            {
                var server = _redis.GetServer(endpoint);
                var keys = server.Keys(pattern: pattern).ToArray();
                
                if (keys.Length > 0)
                {
                    await _db.KeyDeleteAsync(keys);
                    _logger.LogDebug("Removed {Count} keys matching pattern: {Pattern}", keys.Length, pattern);
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to remove cache by pattern: {Pattern}", pattern);
        }
    }
    
    public async Task<bool> ExistsAsync(string key, CancellationToken cancellationToken = default)
    {
        try
        {
            return await _db.KeyExistsAsync(key);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to check existence for key: {Key}", key);
            return false;
        }
    }
    
    public async Task<T?> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null, CancellationToken cancellationToken = default) where T : class
    {
        // EN: Try to get from cache first
        // VI: Thử lấy từ cache trước
        var cached = await GetAsync<T>(key, cancellationToken);
        if (cached != null)
        {
            return cached;
        }
        
        // EN: If not in cache, get from factory and cache it
        // VI: Nếu không có trong cache, lấy từ factory và cache lại
        var value = await factory();
        if (value != null)
        {
            await SetAsync(key, value, expiration, cancellationToken);
        }
        
        return value;
    }
    
    public async Task BlacklistAsync(string key, TimeSpan expiration, CancellationToken cancellationToken = default)
    {
        var blacklistKey = $"{BlacklistPrefix}{key}";
        await SetStringAsync(blacklistKey, "1", expiration, cancellationToken);
        _logger.LogInformation("Blacklisted key: {Key}, Expiration: {Expiration}", key, expiration);
    }
    
    public async Task<bool> IsBlacklistedAsync(string key, CancellationToken cancellationToken = default)
    {
        var blacklistKey = $"{BlacklistPrefix}{key}";
        return await ExistsAsync(blacklistKey, cancellationToken);
    }
}
