using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using System.Text.Json;

namespace MktZaloService.Infrastructure.Caching;

/// <summary>
/// EN: Interface for conversation context caching.
/// VI: Interface cho caching ngữ cảnh cuộc hội thoại.
/// </summary>
public interface IConversationCacheService
{
    /// <summary>
    /// EN: Get conversation context from cache.
    /// VI: Lấy ngữ cảnh cuộc hội thoại từ cache.
    /// </summary>
    Task<ConversationContext?> GetContextAsync(Guid conversationId, CancellationToken ct = default);

    /// <summary>
    /// EN: Set conversation context in cache.
    /// VI: Lưu ngữ cảnh cuộc hội thoại vào cache.
    /// </summary>
    Task SetContextAsync(Guid conversationId, ConversationContext context, CancellationToken ct = default);

    /// <summary>
    /// EN: Remove conversation context from cache.
    /// VI: Xóa ngữ cảnh cuộc hội thoại khỏi cache.
    /// </summary>
    Task RemoveContextAsync(Guid conversationId, CancellationToken ct = default);

    /// <summary>
    /// EN: Get or set value with factory pattern.
    /// VI: Lấy hoặc tạo giá trị với factory pattern.
    /// </summary>
    Task<T?> GetOrSetAsync<T>(
        string key,
        Func<Task<T>> factory,
        TimeSpan expiration,
        CancellationToken ct = default) where T : class;
}

/// <summary>
/// EN: Conversation context for caching.
/// VI: Ngữ cảnh cuộc hội thoại để caching.
/// </summary>
public class ConversationContext
{
    public Guid ConversationId { get; set; }
    public Guid CustomerId { get; set; }
    public string ZaloUserId { get; set; } = string.Empty;
    public string CustomerName { get; set; } = string.Empty;
    public List<CachedMessage> RecentMessages { get; set; } = new();
    public DateTime LastUpdated { get; set; }
}

public class CachedMessage
{
    public string Content { get; set; } = string.Empty;
    public bool IsFromBot { get; set; }
    public DateTime SentAt { get; set; }
}

/// <summary>
/// EN: Redis-based implementation of conversation cache service.
/// VI: Implementation cache cuộc hội thoại dựa trên Redis.
/// </summary>
public class ConversationCacheService : IConversationCacheService
{
    private readonly IDistributedCache _cache;
    private readonly ILogger<ConversationCacheService> _logger;

    private static readonly TimeSpan DefaultExpiration = TimeSpan.FromMinutes(30);

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    };

    public ConversationCacheService(
        IDistributedCache cache,
        ILogger<ConversationCacheService> logger)
    {
        _cache = cache ?? throw new ArgumentNullException(nameof(cache));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    /// <summary>
    /// EN: Get conversation context from cache.
    /// VI: Lấy ngữ cảnh cuộc hội thoại từ cache.
    /// </summary>
    public async Task<ConversationContext?> GetContextAsync(Guid conversationId, CancellationToken ct = default)
    {
        var key = GetConversationKey(conversationId);

        try
        {
            var cached = await _cache.GetStringAsync(key, ct);
            if (string.IsNullOrEmpty(cached))
            {
                return null;
            }

            return JsonSerializer.Deserialize<ConversationContext>(cached, JsonOptions);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex,
                "EN: Failed to get conversation context from cache. VI: Không thể lấy ngữ cảnh từ cache");
            return null;
        }
    }

    /// <summary>
    /// EN: Set conversation context in cache.
    /// VI: Lưu ngữ cảnh cuộc hội thoại vào cache.
    /// </summary>
    public async Task SetContextAsync(Guid conversationId, ConversationContext context, CancellationToken ct = default)
    {
        var key = GetConversationKey(conversationId);

        try
        {
            context.LastUpdated = DateTime.UtcNow;
            var json = JsonSerializer.Serialize(context, JsonOptions);

            var options = new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = DefaultExpiration
            };

            await _cache.SetStringAsync(key, json, options, ct);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex,
                "EN: Failed to set conversation context in cache. VI: Không thể lưu ngữ cảnh vào cache");
        }
    }

    /// <summary>
    /// EN: Remove conversation context from cache.
    /// VI: Xóa ngữ cảnh cuộc hội thoại khỏi cache.
    /// </summary>
    public async Task RemoveContextAsync(Guid conversationId, CancellationToken ct = default)
    {
        var key = GetConversationKey(conversationId);

        try
        {
            await _cache.RemoveAsync(key, ct);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex,
                "EN: Failed to remove conversation context from cache. VI: Không thể xóa ngữ cảnh khỏi cache");
        }
    }

    /// <summary>
    /// EN: Get or set value with factory pattern.
    /// VI: Lấy hoặc tạo giá trị với factory pattern.
    /// </summary>
    public async Task<T?> GetOrSetAsync<T>(
        string key,
        Func<Task<T>> factory,
        TimeSpan expiration,
        CancellationToken ct = default) where T : class
    {
        try
        {
            var cached = await _cache.GetStringAsync(key, ct);
            if (!string.IsNullOrEmpty(cached))
            {
                return JsonSerializer.Deserialize<T>(cached, JsonOptions);
            }

            var value = await factory();
            if (value != null)
            {
                var json = JsonSerializer.Serialize(value, JsonOptions);
                var options = new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = expiration
                };
                await _cache.SetStringAsync(key, json, options, ct);
            }

            return value;
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex,
                "EN: Cache operation failed, falling back to factory. VI: Cache thất bại, sử dụng factory");
            return await factory();
        }
    }

    private static string GetConversationKey(Guid conversationId) => $"conversation:{conversationId}";
}
