using System.Collections;
using CacheManager.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using XF.Domain.Cache;

namespace Hotline.CacheManager
{
    public class DefaultTypedCache<TValue> //: ITypedCache<TValue>
        where TValue : class
    {
        private readonly ICacheManager<TValue> _cache;
        private readonly IServiceScopeFactory _serviceScopeFactory;
        private readonly string _region;

        public DefaultTypedCache(ICacheManager<TValue> cache, IServiceScopeFactory serviceScopeFactory)
        {
            _cache = cache;
            _serviceScopeFactory = serviceScopeFactory;
            _region = CreateRegion();
        }

        public bool Add(string key, TValue value, ExpireMode? expireMode = ExpireMode.None, TimeSpan? timeout = null) =>
            _cache.Add(CreateCacheItem(key, value, expireMode, timeout));

        public void Put(string key, TValue value, ExpireMode? expireMode, TimeSpan? timeout = null) =>
            _cache.Put(CreateCacheItem(key, value, expireMode, timeout));

        public TValue Update(string key, Func<TValue, TValue> updateValue) => _cache.Update(key, _region, updateValue);

        public TValue? Get(string key) => _cache.Get<TValue>(key, _region);

        public void Expire(string key, DateTime absoluteExpiration) => _cache.Expire(key, _region, absoluteExpiration);

        public void Expire(string key, TimeSpan slidingExpiration) => _cache.Expire(key, _region, slidingExpiration);

        public void Expire(string key, ExpireMode? expireMode, TimeSpan? timeout = null) =>
            _cache.Expire(key, _region, SwitchExpireMode(expireMode), timeout ?? TimeSpan.FromMinutes(10));

        public TValue AddOrUpdate(string key, TValue addValue, Func<TValue, TValue> updateValue, ExpireMode? expireMode = ExpireMode.None, TimeSpan? timeout = null) =>
            _cache.AddOrUpdate(CreateCacheItem(key, addValue, expireMode, timeout), updateValue);

        public TValue GetOrAdd(string key, TValue value, ExpireMode? expireMode, TimeSpan? timeout = null) =>
            _cache.GetOrAdd(key, _region, (k, r) => CreateCacheItem(key, value, expireMode, timeout)).Value;

        public TValue GetOrAdd(string key, Func<string, TValue> valueFactory, ExpireMode? expireMode, TimeSpan? timeout = null) =>
            _cache.GetOrAdd(key, _region, (k, r) => CreateCacheItem(key, valueFactory(key), expireMode, timeout)).Value;

        public bool TryGetOrAdd(string key, Func<string, TValue> valueFactory, out TValue? value, ExpireMode? expireMode, TimeSpan? timeout = null)
        {
            var result = _cache.TryGetOrAdd(key,
                _region,
                (k, r) => CreateCacheItem(key, valueFactory(key), expireMode, timeout),
                out var valueItem);

            value = result ? valueItem.Value : default;
            return result;
        }

        public bool TryUpdate(string key, Func<TValue, TValue> updateValue, out TValue? value)
        {
            return _cache.TryUpdate(key, _region, updateValue, out value);
        }

        public bool Remove(string key) => _cache.Remove(key, _region);
        public bool Exists(string key) => _cache.Exists(key, _region);

        public string CreateRegion()
        {
            var valueType = typeof(TValue);
            //if (valueType.IsGenericType)
            //{
            //    throw new NotSupportedException("TCachedValue can not be a Generic Type");
            //}

            //if (valueType == typeof(string))
            //{
            //    throw new NotSupportedException("TCachedValue can not be a string");
            //}

            if (string.IsNullOrWhiteSpace(valueType.FullName))
            {
                throw new NotSupportedException("FullName of type TCachedValue can not be null");
            }

            using var scope = _serviceScopeFactory.CreateScope();
            var options = scope.ServiceProvider.GetRequiredService<IOptionsSnapshot<CacheOptions>>();
            var prefix = options.Value.Prefix;

            //if (valueType.TryGetCustomAttribute<CacheAttribute>(false, out var attr))
            //{
            //    if (!string.IsNullOrEmpty(attr.Region))
            //    {
            //        return attr.Region;
            //    }
            //}

            string region;

            if (valueType.IsGenericType && valueType.GetInterfaces().Any(d => d == typeof(IEnumerable)))
            {
                region = "Collection";
            }
            else
            {
                region = valueType.FullName.Replace('.', ':');
            }

            if (!string.IsNullOrEmpty(prefix))
            {
                prefix += ':';
                if (!region.StartsWith(prefix))
                {
                    region = prefix + region;
                }
            }
            return region;
        }

        private static ExpirationMode SwitchExpireMode(ExpireMode? expireMode) =>
            expireMode switch
            {
                ExpireMode.None => ExpirationMode.None,
                ExpireMode.Sliding => ExpirationMode.Sliding,
                ExpireMode.Absolute => ExpirationMode.Absolute,
                null => ExpirationMode.None,
                _ => throw new ArgumentOutOfRangeException(nameof(expireMode), expireMode, null)
            };

        private CacheItem<TValue> CreateCacheItem(string key, TValue value, ExpireMode? expireMode, TimeSpan? timeout) =>
            new CacheItem<TValue>(key, _region, value, SwitchExpireMode(expireMode), timeout ?? TimeSpan.FromMinutes(10));

    }
}
