using FastMember;
using Knoks.Core;
using Knoks.Core.Entities.Args;
using Knoks.Core.Logic.Interfaces;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Reflection;

namespace Knoks.Api.Filters
{
    public class AutoInitFieldActionFilter : Attribute, IActionFilter
    {
        private readonly ILogger _logger;
        
        public AutoInitFieldActionFilter(ILogger<AutoInitFieldActionFilter> logger)
        {
            _logger = logger;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var request = context.HttpContext.Request;
            if (request.Method != "POST" && request.Method != "PUT")
                return;

            var apiBaseController = context.GetApiBaseController();
            var apiConsumer = apiBaseController.ContextApiConsumer;
            var userId = apiBaseController.ContextUserId;
            var operatorId = apiBaseController.ContextOperatorId;

            foreach (var parameter in context.ActionDescriptor.Parameters.Select(p => (ControllerParameterDescriptor)p))
            {
                if (parameter.BindingInfo == null)
                    throw new InvalidOperationException($"The '{parameter.Name}' has no http binding source.");

                //if (!parameter.ParameterInfo.IsDefined(typeof(FromBodyAttribute)) && !parameter.ParameterInfo.IsDefined(typeof(FromFormAttribute)))
                //    continue;

                if (parameter.BindingInfo.BindingSource != BindingSource.Body && parameter.BindingInfo.BindingSource != BindingSource.Form)
                    continue;

                if (!context.ActionArguments.ContainsKey(parameter.Name)) {
                    continue;//it is not the problem of this code if value is empty
                    /*if (context.ModelState.ErrorCount > 0) {
                        throw new Core.Logic.Managers.ValidationException("Validation error", context.ModelState.Where(it => it.Value.Errors.Any()).ToDictionary(it => it.Key, it => string.Join(" ", it.Value.Errors.Select(error => error.ErrorMessage))));
                    }*/
                }
                var obj = context.ActionArguments[parameter.Name];
                if (obj is IFormFile)
                    continue;

                if (!(obj is IApiArgs))
                    throw new InvalidOperationException($"{parameter.Name} not implement {nameof(IApiArgs)}");

                var objType = obj.GetType();
                var accessor = TypeAccessor.Create(objType);
                foreach (var member in accessor.GetMembers().Where(m => m.IsDefined(typeof(AutoInitFieldAttribute))))
                {
                    var attribute = objType.GetProperty(member.Name).GetCustomAttribute<AutoInitFieldAttribute>();

                    if (CheckTypes(FieldType.UserId, attribute, member, typeof(long), typeof(long?)))
                    {
                        if (userId != null)
                            accessor[obj, member.Name] = userId.Value;
                    }
                    if (CheckTypes(FieldType.OperatorId, attribute, member, typeof(int), typeof(int?)))
                    {
                        if (operatorId != null)
                            accessor[obj, member.Name] = operatorId.Value;
                    }
                    else if (CheckTypes(FieldType.ApiConsumerId, attribute, member, typeof(int), typeof(int?)))
                    {
                        if (apiConsumer != null)
                            accessor[obj, member.Name] = apiConsumer.Id;
                    }
                    else if (CheckTypes(FieldType.HttpRequestInfo, attribute, member, typeof(string)))
                    {
                        accessor[obj, member.Name] = request.HttpRequestInfo().ToJson();
                    }
                    else if (CheckTypes(FieldType.CreateIPAddress, attribute, member, typeof(string)))
                    {
                        accessor[obj, member.Name] = request.HttpContext.Connection.RemoteIpAddress?.ToString();
                    }
                }
            }
        }

        private static bool CheckTypes(FieldType checkFieldType, AutoInitFieldAttribute attribute, Member member, params Type[] validTypes)
        {
            if (attribute.FieldType.HasValue)
            {
                if (attribute.FieldType.Value != checkFieldType)
                    return false;
            }
            else
            {
                if (!member.Name.Equals(checkFieldType.ToString(), StringComparison.OrdinalIgnoreCase))
                    return false;
            }

            if (!validTypes.Any(type => type == member.Type))
                throw new InvalidCastException($"{nameof(AutoInitFieldActionFilter)}: The property with {checkFieldType} attribute must be of {string.Join(" or ", validTypes.Select(type => type.Name))} type(s)");

            return true;
        }
    }
}
