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

namespace StorageService.Infrastructure.Caching;

/// <summary>
/// EN: Redis cache service implementation using StackExchange.Redis.
/// VI: Implementation của Redis cache service sử dụng StackExchange.Redis.
/// </summary>
public class RedisCacheService : IRedisCacheService, IAsyncDisposable
{
    private readonly ILogger<RedisCacheService> _logger;
    private readonly Configuration.RedisSettings _settings;
    private readonly Lazy<ConnectionMultiplexer> _lazyConnection;
    private readonly JsonSerializerOptions _jsonOptions;
    private bool _disposed;

    public RedisCacheService(
        IOptions<Configuration.RedisSettings> options,
        ILogger<RedisCacheService> logger)
    {
        _settings = options.Value;
        _logger = logger;
        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = false
        };

        // EN: Lazy initialization for Redis connection
        // VI: Khởi tạo lazy cho Redis connection
        _lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
        {
            try
            {
                var connectionString = _settings.GetConnectionString();
                _logger.LogInformation("Connecting to Redis: {Host}:{Port}", _settings.Host, _settings.Port);
                return ConnectionMultiplexer.Connect(connectionString);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to connect to Redis");
                throw;
            }
        });
    }

    /// <summary>
    /// EN: Get Redis database instance.
    /// VI: Lấy instance Redis database.
    /// </summary>
    private IDatabase Database => _lazyConnection.Value.GetDatabase(_settings.Database);

    /// <inheritdoc />
    public bool IsConnected => _lazyConnection.IsValueCreated && _lazyConnection.Value.IsConnected;

    /// <inheritdoc />
    public async Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default) where T : class
    {
        if (!_settings.Enabled)
        {
            _logger.LogDebug("Redis caching is disabled");
            return default;
        }

        try
        {
            var value = await Database.StringGetAsync(key);
            if (value.IsNullOrEmpty)
            {
                _logger.LogDebug("Cache miss for key: {Key}", key);
                return default;
            }

            _logger.LogDebug("Cache hit for key: {Key}", key);
            return JsonSerializer.Deserialize<T>(value.ToString(), _jsonOptions);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Error getting cache key: {Key}", key);
            return default;
        }
    }

    /// <inheritdoc />
    public async Task SetAsync<T>(
        string key,
        T value,
        TimeSpan? expiry = null,
        CancellationToken cancellationToken = default) where T : class
    {
        if (!_settings.Enabled)
        {
            _logger.LogDebug("Redis caching is disabled");
            return;
        }

        try
        {
            var serialized = JsonSerializer.Serialize(value, _jsonOptions);
            await Database.StringSetAsync(key, serialized, expiry);
            _logger.LogDebug("Cache set for key: {Key}, TTL: {Expiry}", key, expiry);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Error setting cache key: {Key}", key);
        }
    }

    /// <inheritdoc />
    public async Task<bool> DeleteAsync(string key, CancellationToken cancellationToken = default)
    {
        if (!_settings.Enabled)
        {
            return false;
        }

        try
        {
            var result = await Database.KeyDeleteAsync(key);
            _logger.LogDebug("Cache deleted for key: {Key}, Result: {Result}", key, result);
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Error deleting cache key: {Key}", key);
            return false;
        }
    }

    /// <inheritdoc />
    public async Task DeleteByPatternAsync(string pattern, CancellationToken cancellationToken = default)
    {
        if (!_settings.Enabled)
        {
            return;
        }

        try
        {
            var server = _lazyConnection.Value.GetServer(_settings.Host, _settings.Port);
            var keys = server.Keys(pattern: pattern, database: _settings.Database);
            
            var keyArray = keys.ToArray();
            if (keyArray.Length > 0)
            {
                await Database.KeyDeleteAsync(keyArray);
                _logger.LogDebug("Deleted {Count} keys matching pattern: {Pattern}", keyArray.Length, pattern);
            }
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Error deleting cache keys by pattern: {Pattern}", pattern);
        }
    }

    /// <inheritdoc />
    public async Task<bool> ExistsAsync(string key, CancellationToken cancellationToken = default)
    {
        if (!_settings.Enabled)
        {
            return false;
        }

        try
        {
            return await Database.KeyExistsAsync(key);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Error checking cache key existence: {Key}", key);
            return false;
        }
    }

    /// <inheritdoc />
    public async Task<T?> GetOrSetAsync<T>(
        string key,
        Func<Task<T?>> factory,
        TimeSpan? expiry = 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 is not null)
        {
            return cached;
        }

        // EN: Cache miss - execute factory
        // VI: Cache miss - thực thi factory
        var value = await factory();
        if (value is not null)
        {
            await SetAsync(key, value, expiry, cancellationToken);
        }

        return value;
    }

    /// <summary>
    /// EN: Dispose Redis connection.
    /// VI: Giải phóng Redis connection.
    /// </summary>
    public async ValueTask DisposeAsync()
    {
        if (_disposed)
        {
            return;
        }

        if (_lazyConnection.IsValueCreated)
        {
            await _lazyConnection.Value.CloseAsync();
            _lazyConnection.Value.Dispose();
        }

        _disposed = true;
        GC.SuppressFinalize(this);
    }
}
