using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Unity;
using VCommon.Collections;
using VCommon.Ioc;
using VCommon.Logging;
using VCommon.Reflection;
using VCommon.VApplication.Auditing.DataAnnotations;
using VCommon.VApplication.Authorization;
using VCommon.VApplication.Dto;
using VCommon.VEntity;

namespace VCommon.VApplication.Auditing
{
    public abstract class ApplicationServiceAuditInterceptorBase : IInterceptor, ITransientIocClass
    {
        private Guid? _beforeTenantId;
        private Guid? _beforeUserId;

        [Dependency] public IVSession Session { get; set; }
        [Dependency] public IIocManager IocManager { get; set; }
        [Dependency] public ServiceInvokeTiming InvokeTiming { get; set; }

        //built-in PreProcess Interceptor

        protected static void InputValidationIntercept(IReadOnlyDictionary<string, object> paramDic)
        {
            var vPass = true;
            var vResult = new List<ValidationResult>();
            foreach (var kvArg in paramDic.Where(p => null != p.Value))
            {
                var objToValidation = kvArg.Value;
                var context = new ValidationContext(objToValidation);
                //validation by system
                vPass &= Validator.TryValidateObject(objToValidation, context, vResult);
                //validation by custom implement if has
                if (objToValidation is ICustomValidation cv) vPass &= cv.CustomValidation(vResult);
            }

            //collect results and throw
            if (false == vPass)
            {
                throw new VApplicationModelValidationException("模型校验失败", vResult);
            }
        }

        protected void AuthPermissionIntercept(Guid? tenantId, Guid? userId, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
        {
            var svcAuthInClass = svcClass.GetCustomAttributeIncludeInterface<VServiceAuthorizeAttribute>();
            if (0 != svcAuthInClass.Length) Session.DemandAuth();

            var svcAuthInMethod = svcMethod.GetCustomAttribute<VServiceAuthorizeAttribute>(true);
            if (null != svcAuthInMethod) Session.DemandAuth();

            if (false == IocManager.IsRegistered<IPermissionManager>()) return;

            if (0 == svcAuthInClass.Length && null == svcAuthInMethod) return;

            if (0 == svcAuthInClass.Sum(p => p.AnyPermissionsRequired.Length)
                && 0 == (svcAuthInMethod?.AnyPermissionsRequired.Length ?? 0)) return;

            var permMgr = IocManager.Resolve<IPermissionManager>();

            if ((0 == svcAuthInClass.Length || permMgr.CheckPermission(Session.GetTenantId(), Session.GetUserId(), svcAuthInClass.SelectMany(p => p.AnyPermissionsRequired).ToArray())) &&
                (null == svcAuthInMethod || permMgr.CheckPermission(Session.GetTenantId(), Session.GetUserId(), svcAuthInMethod.AnyPermissionsRequired))) return;

            var vrwFriendlyException = new VApplicationAuthException("拒绝访问:您没有权限进行此操作", AuthReason.AccessDenied);

            PreWriteAuditLog(tenantId, userId, svcClass, svcMethod, paramDic, null, vrwFriendlyException);

            throw vrwFriendlyException;
        }

        protected void AuthOrgIntercept(Guid? tenantId, Guid? userId, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
        {
            var mustHaveOrg = paramDic.Values.OfType<IMustHaveOrg>().ToArray();
            if (mustHaveOrg.Length == 0) return;

            if (false == IocManager.IsRegistered<IOrgManager>()) return;

            var orgMgr = IocManager.Resolve<IOrgManager>();
            if (false == orgMgr.Enabled) return;

            var uvOrg = orgMgr.GetUserVisionOrg(Session.GetTenantId(), Session.GetUserId()).ToArray();
            if (mustHaveOrg.All(argument => argument.OrgId.In(uvOrg))) return;

            var vrwFriendlyException = new VApplicationAuthException("拒绝访问:您没有权限操作此对象", AuthReason.AccessDenied);

            PreWriteAuditLog(tenantId, userId, svcClass, svcMethod, paramDic, null, vrwFriendlyException);

            throw vrwFriendlyException;
        }

        //built-in PostProcess LogAction

        protected void LogServiceInvoke(Guid? tenantIdBefore, Guid? userIdBefore, Guid? tenantIdAfter, Guid? userIdAfter, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic, object returnValue, Exception exception)
        {
            object ex = exception;

            var tenantIdToLog = tenantIdAfter ?? tenantIdBefore;
            var userIdToLog = userIdAfter ?? userIdBefore;

            //如果非登录或退出时(即有一边为null)用户标识变化,那就见鬼了,要记下来
            if (null != tenantIdBefore && null != tenantIdAfter && tenantIdBefore != tenantIdAfter
                || null != userIdBefore && null != userIdAfter && userIdBefore != userIdAfter)
            {
                Logger.Warn("Unexpected user id change", new
                {
                    tenantIdBefore,
                    tenantIdAfter,
                    userIdBefore,
                    userIdAfter
                });
            }

            PreWriteAuditLog(tenantIdToLog, userIdToLog, svcClass, svcMethod, paramDic, returnValue, ex);
        }

        //ovr able

        public void BeforeInvoke(Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
        {
            _beforeTenantId = Session.TenantId;
            _beforeUserId = Session.UserId;

            BeforeInvokeService(_beforeTenantId, _beforeUserId, svcClass, svcMethod, paramDic);
        }

        public void AfterInvoke(Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic, object returnValue, Exception exception)
        {
            AfterInvokeService(_beforeTenantId, _beforeUserId, Session.TenantId, Session.UserId, svcClass, svcMethod, paramDic, returnValue, exception);
        }

        public virtual void BeforeInvokeService(Guid? tenantId, Guid? userId, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
        {
            InputValidationIntercept(paramDic);
            AuthPermissionIntercept(tenantId, userId, svcClass, svcMethod, paramDic);
            AuthOrgIntercept(tenantId, userId, svcClass, svcMethod, paramDic);
        }

        public virtual void AfterInvokeService(Guid? tenantIdBefore, Guid? userIdBefore, Guid? tenantIdAfter, Guid? userIdAfter, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic, object returnValue, Exception exception)
        {
            LogServiceInvoke(tenantIdBefore, userIdBefore, tenantIdAfter, userIdAfter, svcClass, svcMethod, paramDic, returnValue, exception);
        }

        protected virtual void PreWriteAuditLog(Guid? tenantId, Guid? userId, Type serviceClass, MethodBase serviceMethod, IReadOnlyDictionary<string, object> input, object output, object exception)
        {
            if (serviceClass.IsDefinedIncludeInterface(typeof(DisableServiceAuditingLogAttribute)) || serviceMethod.IsDefined(typeof(DisableServiceAuditingLogAttribute))) return;

            WriteAuditLog(tenantId, userId, serviceClass.FullName, serviceMethod.Name, input, output, exception);
        }

        //abstract

        protected abstract void WriteAuditLog(Guid? tenantId, Guid? userId, string serviceClassName, string serviceMethodName, IReadOnlyDictionary<string, object> input, object output, object exception);
    }
}
