using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using StackExchange.Redis;
using System.Text.Json;
namespace Core.Blueprint.Redis
{
    /// 
    /// Redis cache provider for managing cache operations.
    /// 
    public sealed class RedisCacheProvider : IRedisCacheProvider
    {
        private IDatabase _cacheDatabase = null!;
        private readonly ILogger _logger;
        private readonly bool _useRedis;
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The Redis connection string.
        /// The logger instance for logging operations.
        /// Thrown when connection string is null or empty.
        public RedisCacheProvider(string connectionString, ILogger logger, IConfiguration configuration)
        {
            if (string.IsNullOrWhiteSpace(connectionString))
                throw new ArgumentNullException(nameof(connectionString), "Redis connection string cannot be null or empty.");
            _logger = logger;
            _useRedis = configuration.GetValue("UseRedisCache", false);
            _cacheDatabase = InitializeRedisAsync(connectionString).GetAwaiter().GetResult();
        }
        /// 
        /// Initializes and establishes a connection to Redis based on the environment.
        /// Uses a local connection in development, and Azure with token credentials in other environments.
        /// 
        /// The Redis connection string.
        /// An  instance representing the Redis cache database.
        /// Thrown when the connection to Redis fails.
        async Task InitializeRedisAsync(string connectionString)
        {
            try
            {
                if (_useRedis)
                {
                    var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty;
                    ConnectionMultiplexer connectionMultiplexer;
                    if (environment.Equals("Local", StringComparison.OrdinalIgnoreCase))
                    {
                        connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(connectionString);
                    }
                    else
                    {
                        var configurationOptions = await ConfigurationOptions.Parse(connectionString)
                            .ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential());
                        configurationOptions.AbortOnConnectFail = false;
                        connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(configurationOptions);
                    }
                    _logger.LogInformation("Successfully connected to Redis.");
                    return connectionMultiplexer.GetDatabase();
                }
                return null;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error establishing Redis connection.");
                throw;
            }
        }
        /// 
        /// Retrieves a cache item by its key.
        /// 
        /// The type of the cached item.
        /// The cache key.
        /// The cached item of type , or default if not found.
        public async ValueTask GetAsync(string key)
        {
            try
            {
                if (_useRedis is not false)
                {
                    var value = await _cacheDatabase.StringGetAsync(key);
                    if (value.IsNullOrEmpty)
                    {
                        _logger.LogInformation($"Cache miss for key: {key}");
                        return default;
                    }
                    _logger.LogInformation($"Cache hit for key: {key}");
                    return JsonSerializer.Deserialize(value);
                }
                return default;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Error getting cache item with key {key}");
                throw;
            }
        }
        /// 
        /// Sets a cache item with the specified key and value.
        /// 
        /// The type of the item to cache.
        /// The cache key.
        /// The item to cache.
        /// The optional expiration time for the cache item.
        public async ValueTask SetAsync(string key, TEntity value, TimeSpan? expiry = null)
        {
            try
            {
                if (_useRedis is not false)
                {
                    var json = JsonSerializer.Serialize(value);
                    await _cacheDatabase.StringSetAsync(key, json, expiry);
                    _logger.LogInformation($"Cache item set with key: {key}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Error setting cache item with key {key}");
                throw;
            }
        }
        /// 
        /// Removes a cache item by its key.
        /// 
        /// The cache key.
        public async ValueTask RemoveAsync(string key)
        {
            try
            {
                if (_useRedis is not false)
                {
                    await _cacheDatabase.KeyDeleteAsync(key);
                    _logger.LogInformation($"Cache item removed with key: {key}");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Error removing cache item with key {key}");
                throw;
            }
        }
        /// 
        /// Checks if a cache item exists for the specified key.
        /// 
        /// The cache key.
        /// True if the cache item exists; otherwise, false.
        public async ValueTask ExistsAsync(string key)
        {
            try
            {
                if (_useRedis is not false)
                {
                    var exists = await _cacheDatabase.KeyExistsAsync(key);
                    _logger.LogInformation($"Cache item exists check for key: {key} - {exists}");
                }
                return false;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Error checking existence of cache item with key {key}");
                throw;
            }
        }
        /// 
        /// Refreshes the expiration time of a cache item if it exists.
        /// 
        /// The cache key.
        /// The new expiration time for the cache item.
        public async ValueTask RefreshAsync(string key, TimeSpan? expiry = null)
        {
            try
            {
                if (_useRedis is not false)
                {
                    var value = await _cacheDatabase.StringGetAsync(key);
                    if (!value.IsNullOrEmpty)
                    {
                        await _cacheDatabase.StringSetAsync(key, value, expiry);
                        _logger.LogInformation($"Cache item refreshed with key: {key}");
                    }
                    else
                    {
                        _logger.LogWarning($"Cache item with key: {key} does not exist, cannot refresh");
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Error refreshing cache item with key {key}");
                throw;
            }
        }
    }
}