using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using VCommon.Ioc;
using VCommon.Json;
using VCommon.Logging;
using VCommon.Reflection;
using VCommon.VApplication;
using VCommon.VApplication.Auditing;
using VCommon.VOpenApi.Docgen;

namespace VCommon.VOpenApi.VAspNetCore
{
    public class ApiMiddleware
    {
        private static string _basePath;
        private static bool _isDebuggingEnabled;
        private static string _openApiDoc;
        private static IReadOnlyDictionary<string, ApiBind> _routers;

        private static IIocManager _iocManager;

        public static void Init(IReadOnlyDictionary<string, Type> services, string basePath = "/api", IIocManager iocManager = null, bool docGen = false, string title = "Api Set", string version = "1.0", bool isDebuggingEnabled = false)
        {
            _basePath = basePath;
            _isDebuggingEnabled = isDebuggingEnabled;
            _iocManager = iocManager ?? new IocManager();

            //生成路由和调用绑定
            //暂定路由规则(不区分大小写): /<资源>/<方法>

            var apiRoutes = new Dictionary<string, ApiBind>();

            foreach (var service in services)
            {
                var resource = service.Key;
                var interfaceType = service.Value;

                if (false == interfaceType.IsInterface)
                {
                    Logger.Warn("ApiModule: Not an interface type registered", new { resource, interfaceType });
                    continue;
                }

                var methods = interfaceType.GetPublicInstanceMethods();

                foreach (var method in methods)
                {
                    var rawRoute = $"/{resource}/{method.Name}";
                    var route = rawRoute.ToLower();
                    if (apiRoutes.ContainsKey(route))
                    {
                        Logger.Warn("ApiModule: Api method name already exist", route);
                        continue;
                    }

                    var parameterInfos = method.GetParameters();
                    var inputParams = parameterInfos;
                    if (1 < inputParams.Length)
                    {
                        Logger.Warn("ApiModule: Api method params more than 1", new { route, method });
                        continue;
                    }

                    apiRoutes[route] = new ApiBind(method, resource, method.Name, interfaceType, rawRoute);
                }
            }

            _routers = apiRoutes;

            _openApiDoc = docGen
                ? DocGenerator.Generate(title, version, _basePath, _routers.Values)
                : VJsonSerializer.Serialize(new { docGen = false });
        }

        //--------------- instance on every query ----------------

        private readonly RequestDelegate _next;

        public ApiMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            var requestPath = context.Request.Path.Value?.ToLower();

            if (requestPath?.StartsWith(_basePath) != true)
            {
                await _next.Invoke(context);
                return;
            }

            var path = requestPath.Substring(_basePath.Length);
            if (path == "/") //根目录吐出文档
            {
                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(_openApiDoc);
            }
            else if (_routers.TryGetValue(path, out var invokeBind))
            {
                context.Response.ContentType = "application/json";

                //create child container
                using var child = _iocManager.CreateChildren();
                child.RegisterInstanceToContainer(new ServiceInvokeTiming());

                //put session dependency
                child.RegisterInstanceToContainer(context);
                //override session to child container from root
                child.RegisterManually<VAspNetCoreSession>();

                try
                {
                    await invokeBind.Invoke(it => child.Resolve(it), context.Request.Body, context.Response.Body);
                }
                catch (Exception exception)
                {
                    await ErrorHandle(context, exception);
                }
            }
            else
            {
                context.Response.StatusCode = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.WriteAsync("404 Not found");
            }
        }

        protected virtual async Task ErrorHandle(HttpContext context, Exception error)
        {
            //400:请求参数格式错误 VApiArgumentException
            //401:未授权(未登录) VApplicationAuthException 1
            //403:拒绝访问(已登录但没有权限) VApplicationAuthException 2
            //422:模型检查失败 VApplicationModelValidationException
            //500:服务器内部错误 *其他

            context.Response.ContentType = "application/json";
            switch (error)
            {
                case VFriendlyException friendlyException:
                    context.Response.StatusCode = friendlyException.StatusCode;
                    await context.Response.WriteAsync(VJsonSerializer.Serialize(new { friendlyException.Message }));
                    break;
                case VApiArgumentException apiArgumentException:
                    context.Response.StatusCode = 400;
                    await context.Response.WriteAsync(VJsonSerializer.Serialize(new { apiArgumentException.Message }));
                    break;
                case VApplicationModelValidationException modelValidationException:
                    context.Response.StatusCode = 422;
                    await context.Response.WriteAsync(VJsonSerializer.Serialize(new { modelValidationException.Message, modelValidationException.Results }));
                    break;
                case VApplicationAuthException authException:
                    context.Response.StatusCode = authException.Reason == AuthReason.AuthRequired ? 401 : 403;
                    await context.Response.WriteAsync(VJsonSerializer.Serialize(new { authException.Message }));
                    break;
                default:
                    {
                        context.Response.StatusCode = 500;

                        string response;
                        if (false == _isDebuggingEnabled)
                        {
                            response = VJsonSerializer.Serialize(new { Message = "Server internal error" });
                        }
                        else
                        {
                            try
                            {
                                response = VJsonSerializer.Serialize(error);
                            }
                            catch (Exception exception)
                            {
                                Logger.Error("ApiMiddleware:Exception json serializer fail", exception);
                                response = VJsonSerializer.Serialize(new { Message = error.ToString() });
                            }
                        }

                        await context.Response.WriteAsync(response);
                        break;
                    }
            }
        }
    }
}
