using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;

namespace Umbraco.Core.Services
{
    public class PublicAccessService : ScopeRepositoryService, IPublicAccessService
    {
        public PublicAccessService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory)
            : base(provider, repositoryFactory, logger, eventMessagesFactory)
        {
        }

        /// <summary>
        /// Gets all defined entries and associated rules
        /// </summary>
        /// <returns></returns>
        public IEnumerable<PublicAccessEntry> GetAll()
        {
            using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
            {
                var repo = RepositoryFactory.CreatePublicAccessRepository(uow);
                return repo.GetAll();
            }
        }

        /// <summary>
        /// Gets the entry defined for the content item's path
        /// </summary>
        /// <param name="content"></param>
        /// <returns>Returns null if no entry is found</returns>
        public PublicAccessEntry GetEntryForContent(IContent content)
        {
            return GetEntryForContent(content.Path.EnsureEndsWith("," + content.Id));
        }

        /// <summary>
        /// Gets the entry defined for the content item based on a content path
        /// </summary>
        /// <param name="contentPath"></param>
        /// <returns>Returns null if no entry is found</returns>
        /// <remarks>
        /// NOTE: This method get's called *very* often! This will return the results from cache
        /// </remarks>
        public PublicAccessEntry GetEntryForContent(string contentPath)
        {
            //Get all ids in the path for the content item and ensure they all
            // parse to ints that are not -1.
            var ids = contentPath.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(x =>
                {
                    int val;
                    if (int.TryParse(x, out val))
                    {
                        return val;
                    }
                    return -1;
                })
                .Where(x => x != -1)
                .ToList();

            //start with the deepest id
            ids.Reverse();

            using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
            {
                //This will retrieve from cache!                 
                var repo = RepositoryFactory.CreatePublicAccessRepository(uow);

                var entries = repo.GetAll().ToArray();

                foreach (var id in ids)
                {
                    var found = entries.FirstOrDefault(x => x.ProtectedNodeId == id);
                    if (found != null) return found;
                }
            }

            return null;
        }

        /// <summary>
        /// Returns true if the content has an entry for it's path
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        public Attempt<PublicAccessEntry> IsProtected(IContent content)
        {
            var result = GetEntryForContent(content);
            return Attempt.If(result != null, result);
        }

        /// <summary>
        /// Returns true if the content has an entry based on a content path
        /// </summary>
        /// <param name="contentPath"></param>
        /// <returns></returns>
        public Attempt<PublicAccessEntry> IsProtected(string contentPath)
        {
            var result = GetEntryForContent(contentPath);
            return Attempt.If(result != null, result);
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("Use AddRule instead, this method will be removed in future versions")]
        public Attempt<OperationStatus<PublicAccessEntry, OperationStatusType>> AddOrUpdateRule(IContent content, string ruleType, string ruleValue)
        {
            return AddRule(content, ruleType, ruleValue);
        }

        /// <summary>
        /// Adds a rule
        /// </summary>
        /// <param name="content"></param>
        /// <param name="ruleType"></param>
        /// <param name="ruleValue"></param>
        /// <returns></returns>
        public Attempt<OperationStatus<PublicAccessEntry, OperationStatusType>> AddRule(IContent content, string ruleType, string ruleValue)
        {
            var evtMsgs = EventMessagesFactory.Get();
            using (var uow = UowProvider.GetUnitOfWork())
            {
                var repo = RepositoryFactory.CreatePublicAccessRepository(uow);
                var entry = repo.GetAll().FirstOrDefault(x => x.ProtectedNodeId == content.Id);
                if (entry == null)
                {
                    uow.Commit();
                    return Attempt<OperationStatus<PublicAccessEntry, OperationStatusType>>.Fail();
                }
                    

                var existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue);
                if (existingRule == null)
                {
                    entry.AddRule(ruleValue, ruleType);
                }
                else
                {
                    uow.Commit();
                    //If they are both the same already then there's nothing to update, exit
                    return Attempt<OperationStatus<PublicAccessEntry, OperationStatusType>>.Succeed(new OperationStatus<PublicAccessEntry, OperationStatusType>(entry, OperationStatusType.Success, evtMsgs));
                }

                if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs)))
                {
                    uow.Commit();
                    return Attempt<OperationStatus<PublicAccessEntry, OperationStatusType>>.Fail(
                        new OperationStatus<PublicAccessEntry, OperationStatusType>(entry, OperationStatusType.FailedCancelledByEvent, evtMsgs));
                }

                repo.AddOrUpdate(entry);

                uow.Commit();

                uow.Events.Dispatch(Saved, this, new SaveEventArgs<PublicAccessEntry>(entry, false, evtMsgs));
                return Attempt<OperationStatus<PublicAccessEntry, OperationStatusType>>.Succeed(
                    new OperationStatus<PublicAccessEntry, OperationStatusType>(entry, OperationStatusType.Success, evtMsgs));
            }
        }

        /// <summary>
        /// Removes a rule
        /// </summary>
        /// <param name="content"></param>
        /// <param name="ruleType"></param>
        /// <param name="ruleValue"></param>
        public Attempt<OperationStatus> RemoveRule(IContent content, string ruleType, string ruleValue)
        {
            var evtMsgs = EventMessagesFactory.Get();
            using (var uow = UowProvider.GetUnitOfWork())
            {
                var repo = RepositoryFactory.CreatePublicAccessRepository(uow);
                var entry = repo.GetAll().FirstOrDefault(x => x.ProtectedNodeId == content.Id);
                if (entry == null)
                {
                    uow.Commit();
                    return Attempt<OperationStatus>.Fail();
                }

                var existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue);
                if (existingRule == null)
                {
                    uow.Commit();
                    return Attempt<OperationStatus>.Fail();
                }

                entry.RemoveRule(existingRule);

                if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs)))
                {
                    uow.Commit();
                    return OperationStatus.Cancelled(evtMsgs);
                }

                repo.AddOrUpdate(entry);

                uow.Commit();

                uow.Events.Dispatch(Saved, this, new SaveEventArgs<PublicAccessEntry>(entry, false, evtMsgs));
                return OperationStatus.Success(evtMsgs);
            }

        }

        /// <summary>
        /// Saves the entry
        /// </summary>
        /// <param name="entry"></param>
        public Attempt<OperationStatus> Save(PublicAccessEntry entry)
        {
            var evtMsgs = EventMessagesFactory.Get();

            using (var uow = UowProvider.GetUnitOfWork())
            {
                if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs)))
                {
                    uow.Commit();
                    return OperationStatus.Cancelled(evtMsgs);
                }

                var repo = RepositoryFactory.CreatePublicAccessRepository(uow);
                repo.AddOrUpdate(entry);
                uow.Commit();
                uow.Events.Dispatch(Saved, this, new SaveEventArgs<PublicAccessEntry>(entry, false, evtMsgs));
                return OperationStatus.Success(evtMsgs);
            }

           
        }

        /// <summary>
        /// Deletes the entry and all associated rules
        /// </summary>
        /// <param name="entry"></param>
        public Attempt<OperationStatus> Delete(PublicAccessEntry entry)
        {
            var evtMsgs = EventMessagesFactory.Get();

            using (var uow = UowProvider.GetUnitOfWork())
            {
                if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<PublicAccessEntry>(entry, evtMsgs)))
                {
                    uow.Commit();
                    return OperationStatus.Cancelled(evtMsgs);
                }

                var repo = RepositoryFactory.CreatePublicAccessRepository(uow);
                repo.Delete(entry);
                uow.Commit();
                uow.Events.Dispatch(Deleted, this, new DeleteEventArgs<PublicAccessEntry>(entry, false, evtMsgs));
                return OperationStatus.Success(evtMsgs);
            }

            
        }

        /// <summary>
        /// Occurs before Save
        /// </summary>
        public static event TypedEventHandler<IPublicAccessService, SaveEventArgs<PublicAccessEntry>> Saving;

        /// <summary>
        /// Occurs after Save
        /// </summary>
        public static event TypedEventHandler<IPublicAccessService, SaveEventArgs<PublicAccessEntry>> Saved;

        /// <summary>
        /// Occurs before Delete
        /// </summary>		
        public static event TypedEventHandler<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>> Deleting;

        /// <summary>
        /// Occurs after Delete
        /// </summary>
        public static event TypedEventHandler<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>> Deleted;


    }
}