using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using InABox.Clients;
using InABox.Core;
using Newtonsoft.Json.Linq;

namespace InABox.Mobile
{

    public interface IQueueUpdater
    {
        void ProcessQueue();
    }
    
    public interface IQueueUpdater<TEntity> : IQueueUpdater
        where TEntity : Entity, IPersistent, IRemotable, new()
    {
        void Add(TEntity entity);

        TEntity[] Items { get; }
    }

    public class QueuedUpdaterState
    {
        public Guid ID { get; set; }
        public string OriginalState { get; set; }
        public Dictionary<string,object> Changes { get; set; } = new();
        
        public bool DisableUpdates { get; set; }
    }
    
    public class QueueUpdater<TEntity> : IDisposable, IQueueUpdater<TEntity> 
        where TEntity : Entity, IPersistent, IRemotable, new()
    {
        
        private static String StateFile(Guid id) => $"{id}.{typeof(TEntity).Name.Split('.').Last()}";
 
        private Dictionary<TEntity, QueuedUpdaterState> _cache = new();

        private string _path;
        private IModelHost _host;

        public QueueUpdater(IModelHost host, string path)
        {
            _host = host;
            _path = path;
            LoadCache();
            _host.AddUpdateQueue(this);
        }
        
        public void Add(TEntity entity)
        {
            var _state = new QueuedUpdaterState();
            _state.ID = Guid.NewGuid();
            _state.OriginalState = Serialization.Serialize(entity);
            Save(_state);
            _cache[entity] = _state;
            
            entity.PropertyChanged += (sender, args) =>
            {
                var state = _cache[entity];
                state.Changes[args.PropertyName] = CoreUtils.GetPropertyValue(entity,args.PropertyName);
                Save(state);
            };
        }

        public void Update(TEntity entity, Action<TEntity> action)
        {
            var state = _cache[entity];
            state.DisableUpdates = true;
            action(entity);
            Save(state);
            state.DisableUpdates = false;
        }

        private void Save(QueuedUpdaterState state)
        {
            var _text = Serialization.Serialize(state);
            File.WriteAllText(StateFile(state.ID), _text);
        }

        public TEntity[] Items => _cache.Keys.ToArray();
        
        private void LoadCache()
        {
            _cache.Clear();
            var files = Directory.GetFiles(_path, $"*.{typeof(TEntity).Name.Split('.').Last()}");
            foreach (var file in files)
            {
                if (!Guid.TryParse(Path.GetFileNameWithoutExtension(file), out Guid id))
                    continue;
                
                var _text = File.ReadAllText(file);
                if (string.IsNullOrWhiteSpace(_text))
                    continue;
                
                var _state = Serialization.Deserialize<QueuedUpdaterState>(_text);
                if (_state == null)
                    continue;
                
                var _entity = Serialization.Deserialize<TEntity>(_state.OriginalState);
                if (_entity == null)
                    continue;
                
                foreach (var change in _state.Changes) 
                    CoreUtils.SetPropertyValue(_entity,change.Key,change.Value);        
                
                _cache[_entity] = _state;
            }
        }

        public void ProcessQueue()
        {
            var item = Items.FirstOrDefault();
            if (item != null)
            {
                if (item.IsChanged() && _host.IsConnected())
                {
                    new Client<TEntity>().Save(item, "Updated from Mobile Device");
                    var state = Serialization.Serialize(item);
                    File.WriteAllText(StateFile(_cache[item].ID),state);
                }
            }
        }
        
        public void Dispose()
        {
            _host.RemoveUpdateQueue(this);
        }
    }
    
}