using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Utils;
using MongoDB.Bson;

namespace adas_core.Application.Services.Caching
{
    /// <summary>
    /// Orquestador de caché. Selecciona el backend (Redis, InMemory, None)
    /// según CacheSettings y la entidad del key.
    /// Implementa ICacheService y delega en el backend elegido.
    /// </summary>
    public class CacheDispatcher(
        RedisService redis,
        CacheService memory,
        NoCacheService noop,
        CacheSettings cacheSettings)
        : ICacheService
    {
        
        // Selección de backend
        private ICacheService SelectBackend(string key)
        {
            var entity = CacheKeyClassifier.Classify(key);
            var mode = entity switch
            {
                CacheEnum.EntityType.Patients            => cacheSettings.Patients,
                CacheEnum.EntityType.Displays            => cacheSettings.Displays,
                CacheEnum.EntityType.PumpObservations    => cacheSettings.PumpObservations,
                CacheEnum.EntityType.Appointments        => cacheSettings.Appointments,
                CacheEnum.EntityType.GroupedObservations => cacheSettings.GroupedObservations,
                _                                        => CacheEnum.Mode.Cache
            };

            return mode switch
            {
                CacheEnum.Mode.Redis => redis,
                CacheEnum.Mode.Cache => memory,
                _                    => noop
            };
        }

        // Para GroupedObservations generamos la misma clave compuesta que el resto de servicios,
        // de modo que el clasificador y la política de TTL funcionen igual.
        private static string BuildGroupedKey(GroupedField gf, ObjectId patientId)
            => $"GroupedObs:{patientId}:{gf.Name}";

        private ICacheService SelectBackend(GroupedField groupedField, ObjectId patientId)
            => SelectBackend(BuildGroupedKey(groupedField, patientId));

        
        // GetOrSet (KEY string)
        public Task<T> GetOrSetObjectAsync<T>(
            string key,
            Func<Task<T>> factory,
            TimeSpan? ttl = null,
            CancellationToken cancellationToken = default)
            => SelectBackend(key).GetOrSetObjectAsync(key, factory, ttl, cancellationToken);

        public Task<string?> GetOrSetValueAsync(
            string key,
            Func<Task<string>> loader,
            TimeSpan? ttl = null)
            => SelectBackend(key).GetOrSetValueAsync(key, loader, ttl);

        
        // GetOrSet (GroupedField + PatientId)
        public Task<T> GetOrSetObjectAsync<T>(
            GroupedField groupedField,
            ObjectId patientId,
            Func<Task<T>> factory,
            TimeSpan? ttl = null,
            CancellationToken cancellationToken = default)
            => SelectBackend(groupedField, patientId)
                .GetOrSetObjectAsync(groupedField, patientId, factory, ttl, cancellationToken);

        
        // Set/Get básicos
        public void SetValue(string key, string value)
            => SelectBackend(key).SetValue(key, value);

        public string? GetValue(string key)
            => SelectBackend(key).GetValue(key);

        public Task<T?> GetObjectAsync<T>(string key, bool upd = true)
            => SelectBackend(key).GetObjectAsync<T>(key, upd);

        public Task SetObjectAsync<T>(string key, T obj, bool upd = true)
            => SelectBackend(key).SetObjectAsync(key, obj, upd);

        public Task<T?> GetObjectAsync<T>(string key, TimeSpan? ttl, bool upd)
            => SelectBackend(key).GetObjectAsync<T>(key, ttl, upd);

        public Task SetObjectAsync<T>(string key, T obj, TimeSpan? ttl, bool upd)
            => SelectBackend(key).SetObjectAsync(key, obj, ttl, upd);

        public Task DeleteObjectAsync(string key)
            => SelectBackend(key).DeleteObjectAsync(key);

        public async Task<long> DeleteByPatternAsync(string pattern)
            => await redis.DeleteByPatternAsync(pattern) + await memory.DeleteByPatternAsync(pattern);

        public void CleanCache()
        {
            memory.CleanCache();
            redis.CleanCache();
        }
    }
}