using Kit.Helpers;
using Kit.Helpers.Repository.Converter;
using Kit.Helpers.Repository.Xml.Exceptions;
using System;
using System.Linq;
using System.Xml;

namespace Kit.Helpers.Repository.Xml
{
    public abstract class EntityXmlPersistentComplexRepository<TEntity, TKey, TRootKey, TFilter, TContextKey> :
      EntityXmlSelectComplexRepository<TEntity, TFilter, TKey, TRootKey, TContextKey>,
      IPersistentXml2Repository<TEntity, TKey, TFilter, TContextKey>
        where TEntity : XmlComplexClass<TKey, TRootKey, TContextKey>
        where TFilter : IXmlComplexFilter<TContextKey, TRootKey, TKey>
    {
        protected string _elementsAfterPostion;

        public EntityXmlPersistentComplexRepository(string elementName) : base(elementName)
        {
        }

        public EntityXmlPersistentComplexRepository(string elementName, IConverter<XmlNode, TEntity> converter) : base(elementName, converter)
        {
        }

        public EntityXmlPersistentComplexRepository(string rootElementName, string path, string elementName) : base(rootElementName, path, elementName)
        {
        }

        public EntityXmlPersistentComplexRepository(string path, string elementName) : base(path, elementName)
        {
        }

        public EntityXmlPersistentComplexRepository(string path, string elementName, IConverter<XmlNode, TEntity> converter) : base(path, elementName, converter)
        {
        }

        public EntityXmlPersistentComplexRepository(string rootElementName, string path, string elementName, IConverter<XmlNode, TEntity> converter) : base(rootElementName, path, elementName, converter)
        {
        }

        public EntityXmlPersistentComplexRepository(string elementsAfterPostion, string rootElementName, string path, string elementName, IConverter<XmlNode, TEntity> converter) : base(rootElementName, path, elementName, converter)
        {
            _elementsAfterPostion = elementsAfterPostion;
        }

        protected virtual XmlNode GetElementAfterInsert(XmlNode root, XmlNode persistent = null)
        {
            if (!string.IsNullOrEmpty(_elementsAfterPostion))
            {
                var elements = root.SelectNodes(_elementsAfterPostion);
                return elements.Cast<XmlNode>().LastOrDefault();
            }
            return null;
        }


        protected abstract IEnumerable<ItemLog> SetPersistentFromEntity(XmlDocument db, XmlNode persistent, TEntity entity);

        protected virtual bool OnInserting(XmlDocument db, XmlNode persistent, TEntity entity) { return true; }
        protected virtual bool OnInserted(XmlDocument db, XmlNode persistent, TEntity entity) { return true; }

        protected virtual bool OnUpdating(XmlDocument db, XmlNode persistent, TEntity entity) { return true; }
        protected virtual bool OnUpdated(XmlDocument db, XmlNode persistent, TEntity entity, IEnumerable<ItemLog> itemLogs)
        {
            if (itemLogs.IsNullOrEmpty() == false)
            {
                ItemLogEventManager.Invoke(entity.ContentId, itemLogs);
            }

            return true;
        }


        public virtual void Delete(XmlDocument db, TKey key)
        {
            var persistent = SelectNodesByKey(db, key).Cast<XmlNode>().AsQueryable().FirstOrDefault();
            persistent.ParentNode.RemoveChild(persistent);
        }

        protected override sealed XmlNodeList SelectNodesByKey(XmlDocument db, TKey key)
        {
            return base.SelectNodesByKey(db, key);
        }

        public virtual TEntity Get(XmlDocument db, TKey key)
        {
            return Get(db, key, default);
        }
        public virtual TEntity Get(XmlDocument db, TKey key, TFilter filter)
        {
            XmlNodeList list = SelectNodesByKey(db, key);
            return this.FillResult(db, list.Cast<XmlNode>().AsQueryable().ToList().Select(x => this.FillXmlClass(this.ConvertToEntity(x, filter), x, filter)), filter).SingleOrDefault();
        }

        public virtual bool Exists(XmlDocument db, TKey key)
        {
            XmlNodeList list = SelectNodesByKey(db, key);
            return list.Count > 0;
        }

        protected virtual System.Linq.Expressions.Expression<Func<XmlNode, bool>> GetByKeyExpression(TKey key)
        {
            return x => x.Attributes["guid"].InnerText == key.ToString();
        }

        public IEnumerable<ItemLog> Update(XmlDocument db, TEntity entity)
        {
            var persistent = SelectNodesByKey(db, entity.PersistentKey).Cast<XmlNode>().AsQueryable().FirstOrDefault();
            IEnumerable<ItemLog>? itemLogs = null;

            if (this.OnUpdating(db, persistent, entity))
            {
                //int row = persistent.GetValueAttributeInt32("rowVersion");
                int row = entity.RowVersion;
                if (row != entity.RowVersion) throw new RowVersionException("Данные были изменены версии записи не совпадают");

                itemLogs = this.SetPersistentFromEntity(db, persistent, entity);
                persistent.SetAttributeValue("rowVersion", (++row).ToString());
                this.OnUpdated(db, persistent, entity, itemLogs);
            }
            return itemLogs ?? Array.Empty<ItemLog>();
        }

        public TKey Insert(XmlDocument db, TEntity entity)
        {
            XmlNode root = GetRootNode(db, entity.RootId);
            XmlNode parent = GetParentNode(root);
            XmlNode persistent = parent.OwnerDocument.CreateNode(XmlNodeType.Element, _elementName, "");
            this.SetPersistentFromEntity(parent.OwnerDocument, persistent, entity);

            if (this.OnInserting(db, persistent, entity))
            {
                var after = GetElementAfterInsert(parent, persistent);
                if (after == null) parent.InsertBefore(persistent, parent.FirstChild);
                else
                {
                    parent.InsertAfter(persistent, after);
                }
                this.OnInserted(db, persistent, entity);
            }

            return entity.PersistentKey;
        }

        public TKey InsertToNode(XmlNode db, TEntity entity)
        {
            XmlNode parent = GetParentNode(db);
            XmlNode persistent = parent.OwnerDocument.CreateNode(XmlNodeType.Element, _elementName, "");
            this.SetPersistentFromEntity(parent.OwnerDocument, persistent, entity);

            var after = GetElementAfterInsert(parent, persistent);
            if (after == null) parent.InsertBefore(persistent, parent.FirstChild);
            else
            {
                parent.InsertAfter(persistent, after);
            }

            return entity.PersistentKey;
        }
    }
}
