using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.GroupedObservations;
using MongoDB.Bson;
using System.Collections.Concurrent;

namespace adas_core.Application.Services.Caching
{
    /// <summary>
    /// Implementación de caché en memoria con soporte de locking seguro
    /// mediante LockManagerService + InMemoryLockProvider.
    /// Compatible con la interfaz ICacheService incluyendo GetOrSet.
    /// </summary>
    public class CacheService(LockManagerService lockManager) : ICacheService
    {
        private readonly ConcurrentDictionary<string, object> _mem = new();

        // HELPERS
        private static string BuildGroupedKey(GroupedField gf, ObjectId patientId)
            => $"GroupedObs:{patientId}:{gf.Name}";

        
        // GET OR SET (string key)
        public async Task<T> GetOrSetObjectAsync<T>(
            string key,
            Func<Task<T>> factory,
            TimeSpan? ttl = null,
            CancellationToken cancellationToken = default)
        {
            // FAST PATH
            if (_mem.TryGetValue(key, out var existing))
                return (T)existing;

            // LOCKED PATH
            return await lockManager.WithLockAsync(
                $"getorset:{key}",
                TimeSpan.FromSeconds(5),
                async () =>
                {
                    if (_mem.TryGetValue(key, out var again))
                        return (T)again;

                    var created = await factory();

                    if (created != null!)
                        _mem[key] = created;

                    return created!;
                },
                cancellationToken);
        }

        public async Task<string?> GetOrSetValueAsync(
            string key,
            Func<Task<string>> loader,
            TimeSpan? ttlOverride = null)
        {
            
            var result = await GetOrSetObjectAsync(key, loader, ttlOverride);
            return (string?)result;

        }

        
        // GET OR SET (GroupedField + patientId)
        public async Task<T> GetOrSetObjectAsync<T>(
            GroupedField groupedField,
            ObjectId patientId,
            Func<Task<T>> factory,
            TimeSpan? ttl = null,
            CancellationToken cancellationToken = default)
        {
            var key = BuildGroupedKey(groupedField, patientId);

            if (_mem.TryGetValue(key, out var existing))
                return (T)existing;

            return await lockManager.WithLockAsync(
                $"getorset:{key}",
                TimeSpan.FromSeconds(5),
                async () =>
                {
                    if (_mem.TryGetValue(key, out var again))
                        return (T)again;

                    var created = await factory();

                    if (created != null!)
                        _mem[key] = created;

                    return created!;
                },
                cancellationToken);
        }

        
        // GET / SET
        public void SetValue(string key, string value)
            => _mem[key] = value;

        public string? GetValue(string key)
            => _mem.TryGetValue(key, out var v) ? v.ToString() : null;

        public Task<T?> GetObjectAsync<T>(string key, bool updateExpiration = true)
        {
            return Task.FromResult(
                _mem.TryGetValue(key, out var v) ? (T?)v : default
            );
        }

        public Task SetObjectAsync<T>(string key, T obj, bool updateExpiration = true)
        {
            _mem[key] = obj!;
            return Task.CompletedTask;
        }

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

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

        
        // DELETE / CLEAN
        public Task DeleteObjectAsync(string key)
        {
            _mem.TryRemove(key, out _);
            return Task.CompletedTask;
        }

        public Task<long> DeleteByPatternAsync(string pattern)
        {
            var p = pattern.Replace("*", "");
            var keys = _mem.Keys.Where(k => k.Contains(p)).ToList();

            long removed = 0;
            foreach (var k in keys)
                if (_mem.TryRemove(k, out _))
                    removed++;

            return Task.FromResult(removed);
        }

        public void CleanCache() => _mem.Clear();
    }
}