using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VCommon.Json;
using VCommon.Logging;
using VCommon.VOpenApi.Json;

namespace VCommon.VOpenApi
{
    [DebuggerDisplay("ApiBind {RawRoute}")]
    public class ApiBind
    {
        private static readonly JsonReturnValueSerializer ReturnValueSerializer = new JsonReturnValueSerializer();

        private readonly Type _interfaceType;

        private readonly Func<object, object, object> _preCompiledInvoke;

        public string RawRoute { get; }

        public Type ApiResourceType { get; }
        public MethodInfo ApiMethod { get; }
        public Type InputParamType { get; }

        public string ResourceName { get; }
        public string Name { get; }

        public ApiBind(MethodInfo apiMethod, string resourceName, string name, Type interfaceType, string rawRoute)
        {
            _interfaceType = interfaceType;
            ApiMethod = apiMethod;
            ResourceName = resourceName;

            Name = name;
            ApiResourceType = apiMethod.DeclaringType;

            var parameters = ApiMethod.GetParameters();
            if (1 == parameters.Length)
            {
                InputParamType = parameters[0].ParameterType;
            }

            RawRoute = rawRoute;

            //Compile method invoke reduce time
            {
                var hasParam = InputParamType != null;
                var hasReturn = apiMethod.ReturnType != typeof(void);

                var pInstance = Expression.Parameter(typeof(object), "instance");
                var unboxInstance = Expression.Convert(pInstance, interfaceType);
                var pInput = Expression.Parameter(typeof(object), "input");

                MethodCallExpression invoke;
                if (hasParam)
                {
                    var unboxInput = Expression.Convert(pInput, InputParamType);
                    invoke = Expression.Call(unboxInstance, apiMethod, unboxInput);
                }
                else
                {
                    invoke = Expression.Call(unboxInstance, apiMethod);
                }

                if (hasParam)
                {
                    if (hasReturn)
                    {
                        var cvr = Expression.Convert(invoke, typeof(object));
                        var lam = Expression.Lambda<Func<object, object, object>>(cvr, pInstance, pInput);
                        _preCompiledInvoke = lam.Compile();
                    }
                    else
                    {
                        var lam = Expression.Lambda<Action<object, object>>(invoke, pInstance, pInput);
                        var del = lam.Compile();
                        _preCompiledInvoke = (instance, input) =>
                        {
                            del(instance, input);
                            return null;
                        };
                    }
                }
                else
                {
                    if (hasReturn)
                    {
                        var cvr = Expression.Convert(invoke, typeof(object));
                        var lam = Expression.Lambda<Func<object, object>>(cvr, pInstance);
                        var del = lam.Compile();
                        _preCompiledInvoke = (instance, _) => del(instance);
                    }
                    else
                    {
                        var lam = Expression.Lambda<Action<object>>(invoke, pInstance);
                        var del = lam.Compile();
                        _preCompiledInvoke = (instance, _) =>
                        {
                            del(instance);
                            return null;
                        };
                    }
                }
            }
        }

        public async Task Invoke(Func<Type, object> resolveFunc, Stream inputStream, Stream outputStream)
        {
            object inputParam = null;
            if (null != InputParamType)
            {
                //TODO: PACT? Scheme check?

                var inputReader = new StreamReader(inputStream, Encoding.UTF8);
                var input = await inputReader.ReadToEndAsync();

                try
                {
                    inputParam = VJsonSerializer.Deserialize(input, InputParamType);
                }
                catch (JsonReaderException exception)
                {
                    throw new VApiArgumentException("参数错误:" + exception.Message);
                }
                catch (JsonSerializationException exception)
                {
                    throw new VApiArgumentException($"参数错误:{exception.Message}{exception.InnerException?.Message}");
                }
                catch (Exception exception)
                {
                    Logger.Error("ApiBind: Error on parsing input", new { inputJson = input, exception });
                    throw new VApiArgumentException("参数错误");
                }
            }

            var inst = resolveFunc(_interfaceType);

            var returnValue = _preCompiledInvoke(inst, inputParam);

            var output = ReturnValueSerializer.SerializeObject(returnValue);
            var bufOutput = Encoding.UTF8.GetBytes(output);
            await outputStream.WriteAsync(bufOutput, 0, bufOutput.Length);
        }
    }
}
