using Newtonsoft.Json;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using HealthMonitor.Core.Common;
using HealthMonitor.Util.Common.Cache;
using HealthMonitor.Util.Entities.Base;
using HealthMonitor.Util.Models;
using HealthMonitor.Util.Entities.GpsCard;

namespace HealthMonitor.Core.Cache
{
    public class DurableEntityManager : IDurableEntityManager, IDisposable
    {
        private const string DurableCacheKey = "Entity";

        private static object _syncLocker = new object();
        private readonly Dictionary<Type, IEntityCacheHandler> _dictDurableEntities;

        private MD5? _md5;

        public DurableEntityManager()
        {
            var typesDurable = Assembly.GetAssembly(typeof(EntityBase))?.GetTypes()
                .Where(type => !string.IsNullOrEmpty(type.Namespace) && type.Namespace.StartsWith("HealthMonitor.Util.Entities"))
                .Where(type => type.IsSubclassOf(typeof(EntityBase)) && type.GetCustomAttribute<DurableCacheEntityAttr>() != null)
                .ToList();

            _dictDurableEntities = new Dictionary<Type, IEntityCacheHandler>();
            typesDurable?.ForEach(e =>
            {
                var attr = e.GetCustomAttribute<DurableCacheEntityAttr>();
                _dictDurableEntities.Add(e, new EntityCacheHandler(e, attr!.Duration));
            });

            _md5 = MD5.Create();
        }

        public IEntityCacheHandler? GetCacheHandler(Type entityType)
        {
            return _dictDurableEntities.ContainsKey(entityType) ? _dictDurableEntities[entityType] : null;
        }

        public string CalcCacheKey(Type entityType, string id)
        {
            string typeName = entityType.Name;
            return $"{DurableCacheKey}_{typeName}#{id}";
        }

        public string CalcCacheKey(Type entityType, GeneralParam conditions)
        {
            string typeName = entityType.Name;
            string hash = ComputeHash(conditions);
            var key = $"{DurableCacheKey}_{typeName}${hash}";

            return key;
        }

        public string CalcHistoryCacheKey(Type entityType, string id, string imei, DateTime? date = null)
        {
            string typeName = entityType.Name;
            var historyDate = date ?? DateTime.Now;
            if (entityType == typeof(GpsBloodPressReferenceValue)) id = $"{historyDate.ToString("yyyyMM")}_{DeviceUtils.GetImeiSuffix(imei)}_{id}";
            else id = $"{historyDate.ToString("yyyyMM")}_{id}";
            return $"{DurableCacheKey}_{typeName}#{id}";
        }

        public string CalcHistoryCacheKey(Type entityType, GeneralParam conditions, string imei, DateTime? date = null)
        {
            string typeName = entityType.Name;
            var historyDate = date ?? DateTime.Now;
            string hash = ComputeHash(conditions);
            if (entityType == typeof(GpsBloodPressReferenceValue)) hash = $"{historyDate.ToString("yyyyMM")}_{DeviceUtils.GetImeiSuffix(imei)}_{hash}";
            else hash = $"{historyDate.ToString("yyyyMM")}_{hash}";
            var key = $"{DurableCacheKey}_{typeName}${hash}";

            return key;
        }

        public void CleanUpEntitiesMapper()
        {
            foreach (var key in _dictDurableEntities.Keys)
            {
                _dictDurableEntities[key].CleanUpExpiredMapper();
            }
        }

        private string ComputeHash(object data)
        {
            lock (_syncLocker)
            {
                var value = JsonConvert.SerializeObject(data);
                //value = BitConverter.ToString(_md5.ComputeHash(Encoding.UTF8.GetBytes(value)));
                value = Convert.ToBase64String(_md5!.ComputeHash(Encoding.UTF8.GetBytes(value)));
                return value;
            }
        }

        public void Dispose()
        {
            if (_md5 != null)
            {
                _md5.Dispose();
                _md5 = null;
            }
        }
    }
}