using System;
using System.Collections.Concurrent;
using Jellyfin.Plugin.SRFPlay.Api.Models;
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
using Microsoft.Extensions.Logging;

namespace Jellyfin.Plugin.SRFPlay.Services;

/// <summary>
/// Service for caching metadata from SRF API.
/// </summary>
public sealed class MetadataCache : IMetadataCache
{
    private readonly ILogger<MetadataCache> _logger;
    private readonly ConcurrentDictionary<string, CacheEntry<MediaComposition>> _mediaCompositionCache;

    /// <summary>
    /// Initializes a new instance of the <see cref="MetadataCache"/> class.
    /// </summary>
    /// <param name="logger">The logger instance.</param>
    public MetadataCache(ILogger<MetadataCache> logger)
    {
        _logger = logger;
        _mediaCompositionCache = new ConcurrentDictionary<string, CacheEntry<MediaComposition>>();
    }

    /// <summary>
    /// Gets cached media composition by URN.
    /// </summary>
    /// <param name="urn">The URN.</param>
    /// <param name="cacheDurationMinutes">The cache duration in minutes.</param>
    /// <returns>The cached media composition, or null if not found or expired.</returns>
    public MediaComposition? GetMediaComposition(string urn, int cacheDurationMinutes)
    {
        if (string.IsNullOrEmpty(urn))
        {
            return null;
        }

        if (_mediaCompositionCache.TryGetValue(urn, out var entry))
        {
            if (entry.IsValid(cacheDurationMinutes))
            {
                _logger.LogDebug("Cache hit for URN: {Urn}", urn);
                return entry.Value;
            }

            _logger.LogDebug("Cache entry expired for URN: {Urn}", urn);
        }

        return null;
    }

    /// <summary>
    /// Sets media composition in cache.
    /// </summary>
    /// <param name="urn">The URN.</param>
    /// <param name="mediaComposition">The media composition to cache.</param>
    public void SetMediaComposition(string urn, MediaComposition mediaComposition)
    {
        if (string.IsNullOrEmpty(urn) || mediaComposition == null)
        {
            return;
        }

        var entry = new CacheEntry<MediaComposition>(mediaComposition);
        _mediaCompositionCache.AddOrUpdate(urn, entry, (key, oldValue) => entry);
        _logger.LogDebug("Cached media composition for URN: {Urn}", urn);
    }

    /// <summary>
    /// Clears all cached data.
    /// </summary>
    public void Clear()
    {
        _mediaCompositionCache.Clear();
        _logger.LogInformation("Cleared metadata cache");
    }

    /// <summary>
    /// Represents a cached entry with timestamp.
    /// </summary>
    /// <typeparam name="T">The type of cached value.</typeparam>
    private sealed class CacheEntry<T>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CacheEntry{T}"/> class.
        /// </summary>
        /// <param name="value">The value to cache.</param>
        public CacheEntry(T value)
        {
            Value = value;
            Timestamp = DateTime.UtcNow;
        }

        /// <summary>
        /// Gets the cached value.
        /// </summary>
        public T Value { get; }

        /// <summary>
        /// Gets the timestamp when the entry was created.
        /// </summary>
        public DateTime Timestamp { get; }

        /// <summary>
        /// Checks if the cache entry is still valid.
        /// </summary>
        /// <param name="cacheDurationMinutes">The cache duration in minutes.</param>
        /// <returns>True if the entry is still valid.</returns>
        public bool IsValid(int cacheDurationMinutes)
        {
            var expirationTime = Timestamp.AddMinutes(cacheDurationMinutes);
            return DateTime.UtcNow < expirationTime;
        }
    }
}
