using CloudBuilder.Core.DatabaseAccessor;
using CloudBuilder.Core.DatabaseAccessor.Entity;
using CloudBuilder.Core.DependencyInjection.Request;
using CloudBuilder.Core.Policy;
using CloudBuilder.Core.Service;
using CloudBuilder.Gui;
using CloudBuilder.Security.Entity;
using CloudBuilder.Security.Policy;
using CloudBuilder.Security.SearchCriteria;
using DocumentFormat.OpenXml.Spreadsheet;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
namespace CloudBuilder.Security.Service
{
    public class SecurityLockService : ISecurityLockService
    {


        //if key is non string type, it is expected to convert to string before using this process

        //usage:
        //1. UI call Lock to hold entity, exception would be thrown if entity held by others
        //2. upon finish edition, UI call ConsumeLock to free the reserved lock,
        //   exception would be thrown if the lock is overrided either by super user or after expiery

        public const double ACTIVE_WINDOW_MINUTES = -30D;//allow lock 30 minutes

        private SecurityMessagePolicy securityMessagePolicy;
        private readonly IApplicationService service;
        private readonly ILocaleMessageManager localeMessageManager;
        private readonly IContext context;
        private IRepository<SecurityLockEntity> repository;
        public SecurityLockService(IApplicationService service, ILocaleMessageManager localeMessageManager, IContext context)
        {
            this.service = service;
            this.localeMessageManager = localeMessageManager;
            this.context = context;
            repository = service.GetRepository<IRepository<SecurityLockEntity>>();
            securityMessagePolicy = service.ServiceProvider.GetRequiredKeyedService<SecurityMessagePolicy>(typeof(SecurityMessagePolicy).Name); 
        }

        [PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Find)]
        public SecurityLockEntity FindByKey(SecurityLockEntityKey key)
        {
            return repository.DetachedEntities.Where(x => 1 == 1
            && x.EntityName == key.EntityName
            && x.PkeyId == key.PkeyId
            && x.LockedByActor == key.LockedByActor
           ).FirstOrDefault()!;
        }

        //[PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Find)]
        public dynamic FindBySearchCriteria(SecurityLockSearchCriteria criteria)
        {
            throw new NotImplementedException();
        }

        //[PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Print)]
        public dynamic PrintBySearchCriteria(SecurityLockSearchCriteria criteria)
        {
            throw new NotImplementedException();
        }

        [PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Insert)]
        [UnitOfWork]
        public SecurityLockEntity Insert(SecurityLockEntity data)
        {
            repository.InsertNow(data);

            return data;
        }

        [PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Update)]
        [UnitOfWork]
        public SecurityLockEntity Update(SecurityLockEntity data)
        {
            throw new NotImplementedException();
        }


        [PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Delete)]
        public void Delete(SecurityLockEntityKey key)
        {
            SecurityLockEntity ent = repository.DetachedEntities.Where(x => 1 == 1
              && x.EntityName == key.EntityName
              && x.PkeyId == key.PkeyId
              && x.LockedByActor == key.LockedByActor
             ).FirstOrDefault()!;
            if (ent != null) { repository.DeleteNow(ent); }
        }

        [AllowAnonymous]
        public bool IsLocked(string entityName, string key)
        {
            SecurityLockEntity ent = null;
            return IsLocked(entityName, key, out ent);
        }

        public bool IsLocked(string entityName, string key, out SecurityLockEntity ent)
        {
            //find active lock
            ent = FindActiveLock(entityName, key);
            if (ent != null) return true;

            return false;
        }


        private SecurityLockEntity FindActiveLock(string entityName, string key)
        {
            SecurityLockEntity entLock = FindSingleByEntityNameAndPkeyId(entityName, key);

            if (entLock == null) return null;

            DateTime effectiveDt = DateTime.Now.AddMinutes(ACTIVE_WINDOW_MINUTES);
            if (entLock.CreatedDatetime.CompareTo(effectiveDt) < 0)
            {
                //it's an expired lock, remove it
                repository.DeleteNow(entLock);
                return null;
            }

            //it's an effective one
            return entLock;
        }

        private SecurityLockEntity FindSingleByEntityNameAndPkeyId(string entityName, string key)
        {
            SecurityLockEntity[] entLocks = FindByEntityNameAndPkeyId(entityName, key);
            if (entLocks == null || entLocks.Length <= 0) return null;
            return entLocks[0];
        }

        private SecurityLockEntity[] FindByEntityNameAndPkeyId(string entityName, string key)
        {
            SecurityLockEntity[] entLocks = repository.DetachedEntities.Where(x => 1 == 1
                    && x.EntityName == entityName
                    && x.PkeyId == key
                    ).OrderByDescending(x => x.CreatedDatetime).ToArray();

            if (entLocks == null && entLocks.Length <= 0) return null;

            return entLocks;
        }

        [AllowAnonymous]
        public void Lock(string entityName, string key, string username)
        {
            Lock(entityName, key, username, false);
        }

        private void Lock(string entityName, string key, string username, bool forceLock)
        {
            if (!forceLock)
            {
                SecurityLockEntity actLock = FindActiveLock(entityName, key);
                if (actLock != null && !actLock.LockedByActor.Equals(username))
                {
                    //"{0} [{1}] is locked by [{2}] on [{3}]";
                    //throw new ValidatedException(entityName + " [" + key + "] is locked by [" + actLock.LockedByActor + "] on [" + actLock.CreatedDatetime + "]");
                    ISecurityUserService securityLockService = service.ServiceProvider.GetService<ISecurityUserService>()!;
                    var user = securityLockService.FindByStaffId(actLock.LockedByActor);
                    string lockedByActor = actLock.LockedByActor;

                    if (user != null) lockedByActor = user.UserLocalName;

                    throw new ValidatedException(securityMessagePolicy.ComposeMessage(SecurityMessagePolicy.LOCKED, localeMessageManager.ComposeMessage(typeof(SecurityLockService).Name, entityName), key,
                      lockedByActor,
                        actLock.CreatedDatetime.GetFormattedDateString("yyyy-MM-dd HH:mm:ss")));
                }

            }

            ClearLock(entityName, key);


            SecurityLockEntity newLock = new SecurityLockEntity();
            newLock.EntityName = entityName;
            newLock.PkeyId = key;
            newLock.LockedByActor = username;
            Insert(newLock);
        }

        [AllowAnonymous]
        public void ForceLock(string entityName, string key, string username)
        {
            Lock(entityName, key, username, true);
        }

        [AllowAnonymous]
        public void ConsumeLock(string entityName, string key, string username)
        {
            SecurityLockEntity actLock = FindActiveLock(entityName, key);
            if (actLock == null) return;
            if (!actLock.LockedByActor.Equals(username))
            {
                ISecurityUserService securityLockService = service.ServiceProvider.GetService<ISecurityUserService>()!;
                var user = securityLockService.FindByStaffId(actLock.LockedByActor);
                string lockedByActor = actLock.LockedByActor;

                if (user != null) lockedByActor = user.UserLocalName;

                throw new ValidatedException(securityMessagePolicy.ComposeMessage(SecurityMessagePolicy.EXPIRED, entityName, key, lockedByActor, actLock.CreatedDatetime));
                //throw new ValidatedException("Your lock is expired or force removed. " + entityName + " [" + key + "] is locked by [" + actLock.LockedByActor + "] on [" + actLock.CreatedDatetime + "]");
            }

            ClearLock(entityName, key);
        }

        [AllowAnonymous]
        public void ClearLock(string entityName, string key)
        {
            //find all the locks, then purge
            SecurityLockEntity[] entities = FindByEntityNameAndPkeyId(entityName, key);
            if (entities == null || entities.Length <= 0) return;

            repository.DeleteNow(entities);
        }
    }
}
