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

namespace Kit.Helpers.Repository.Xml
{
    public abstract class XmlSimpleClass<TKey, TContentKey> : IPersistentKey<TKey>, IContentXmlKey<TContentKey>
    {
        public TKey Id { get; set; }
        public TContentKey ContentId { get; set; }
        public TKey PersistentKey
        {
            get { return this.Id; }
        }

        public TContentKey GetContentKey
        {
            get { return this.ContentId; }
        }

        public int RowVersion { get; set; }
    }

    public abstract class XmlSimpleClassDb : XmlSimpleClass<Guid, int> { }

    public abstract class EntityXmlPersistentSimpleRepository<TEntity, TKey, TFilter, TContextKey> :
      EntityXmlSelectSimpleRepository<TEntity, TFilter, TKey, TContextKey>,
      IPersistentXml2Repository<TEntity, TKey, TFilter, TContextKey>
        where TEntity : XmlSimpleClass<TKey, TContextKey>
        where TFilter : IXmlSimpleFilter<TContextKey, TKey>
    {
        protected string _elementsAfterPostion;

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

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

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

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

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

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

        public EntityXmlPersistentSimpleRepository(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; }

        public TKey Insert(XmlDocument db, TEntity entity)
        {
            XmlNode parent = GetParentNode(db);
            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)
                {
                    if (parent.FirstChild == null)
                    {
                        parent.AppendChild(persistent);
                    }
                    else
                    {
                        parent.InsertBefore(persistent, parent.FirstChild);
                    }
                }
                else
                {
                    parent.InsertAfter(persistent, after);
                }
                this.OnInserted(db, persistent, entity);
            }

            return entity.PersistentKey;
        }


        public virtual 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;
        }

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

        private XmlNodeList SelectNodesByKey(XmlDocument db, TKey key)
        {
            string xPath = $"{_path}/{_elementName}";

            if (xPath.StartsWith("/") == false)
            {
                xPath = "//" + xPath;
            }

            XmlNodeList list = db.SelectNodes($"{xPath}[@guid=\"{key}\"]");
            if (list.Count == 0)
            {
                list = db.SelectNodes($"{xPath}[@guid=\"{key.ToString().ToUpper()}\"]");
            }

            return list;
        }

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

        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)
        {
            XmlNode 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);
            }
            return itemLogs ?? Array.Empty<ItemLog>();
        }
    }
}