using Microsoft.Extensions.Caching.Memory;
using Kit.Helpers;

namespace Kit.Helpers.Cache
{
    public abstract class BaseCacheProvider : ICacheProvider
    {

        private readonly Dictionary<string, object> _lockObjects = new Dictionary<string, object>(1000);

        private readonly object _keysLock = new object();

        public abstract bool Contains(string cacheName);
        public abstract void PurgeCacheItems(string cacheName, bool isPrefix = false);
        public abstract void SetCacheData<TObject>(string cacheName, TObject data, MemoryCacheEntryOptions? setProperties = null, PostEvictionDelegate? callback = null);
        public abstract TResult GetCacheData<TResult>(string cacheName);

        protected string CacheNameNormalize(string casheName)
        {
            if (casheName == null) return string.Empty;

            return casheName.ToLower();
        }
        protected object GetLockObject(string cacheName)
        {
            lock (_keysLock)
            {
                if (!this._lockObjects.ContainsKey(cacheName)) this._lockObjects.Add(cacheName, new object());
                return this._lockObjects[cacheName];
            }
        }

        public virtual string CreateCacheName(object prefix, params object[] args)
        {
            prefix = prefix != null ? prefix.ToString()!.AddSufixIsNotEmpty("_") : string.Empty;

            string end = string.Empty;
            if (!args.IsNullOrEmpty())
            {
                end = args.Where(x => x != null).Select(x => x.ToString()).Join("_");
            }

            return $"{prefix}{end}";
        }

        public virtual TResult GetCacheData<TResult>(string cacheName, Func<TResult> getMethod, MemoryCacheEntryOptions setProperties = null)
        {
            cacheName = CacheNameNormalize(cacheName);

            Check.ArgumentIsNotNull(getMethod, "getMethod");
            if (cacheName.IsNullOrEmpty()) return (TResult)getMethod();

            // если есть данные в кеше но возвращаем
            TResult data = GetCacheData<TResult>(cacheName); 
            if (data != null) return data;

            lock (this.GetLockObject(cacheName))
            {
                // повторная проверка после блокировки 
                data = this.GetCacheData<TResult>(cacheName);
                if (data != null) return data;

                // если в кеше данных нет то выполняем метод
                data = getMethod();
                this.SetCacheData(cacheName, data, setProperties);
            }

            return data;
        }

        public TResult GetCacheData<TResult>(string cacheName, Func<TResult> getMethod, MemoryCacheEntryOptions? setProperties = null, PostEvictionDelegate? callback = null)
        {
            cacheName = CacheNameNormalize(cacheName);

            Check.ArgumentIsNotNull(getMethod, "getMethod");
            if (cacheName.IsNullOrEmpty()) return (TResult)getMethod();

            // если есть данные в кеше но возвращаем
            TResult data = GetCacheData<TResult>(cacheName);
            if (data != null) return data;

            lock (this.GetLockObject(cacheName))
            {
                // повторная проверка после блокировки 
                data = this.GetCacheData<TResult>(cacheName);
                if (data != null) return data;

                // если в кеше данных нет то выполняем метод
                data = getMethod();
                this.SetCacheData(cacheName, data, setProperties, callback);
            }

            return data;
        }
    }
}
