//using Knoks.Api.Client;
using Knoks.Api.Controllers.Base;
using Knoks.Core.Entities;
using Knoks.Core.Entities.Args;
using Knoks.Test.Integrations.Api.Tools;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Routing;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using Xunit;

namespace Knoks.Test.Integrations.Api
{
    public class ApiSpecTest : ApiTestClient
    {
        [Fact(DisplayName = "ApiConsistenceTest")]
        void ApiConsistenceTest()
        {
            var controllerMethods = typeof(ApiBaseController).GetTypeInfo().Assembly.GetTypes()
                .Where(type => typeof(ApiBaseController).IsAssignableFrom(type))
                .Select(controller => controller.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
                .Where(m =>
                {
                    if (m.IsSpecialName) return false; //exclude getters and setters

                    var apiExplorerSettingsAttribute = m.GetCustomAttribute<ApiExplorerSettingsAttribute>();
                    return (apiExplorerSettingsAttribute == null || !apiExplorerSettingsAttribute.IgnoreApi);
                }));

            var methods = new List<MethodInfo>();
            foreach (var controller in controllerMethods)
                foreach (var method in controller)
                    methods.Add(method);

            foreach (var method in methods)
            {
                Assert.True(method.IsDefined(typeof(HttpMethodAttribute)),
                    $"The '{nameof(HttpMethodAttribute)}' (GET, POST, PUT) is NOT defined for public {method.DeclaringType.Name}.{method.Name} method.");

                foreach (var pi in method.GetParameters())
                {
                    var hasBinding = pi.GetCustomAttributes().Any(a => a is IBindingSourceMetadata);
                    Assert.True(hasBinding,
                        $"Paramenter '{pi.Name}' in {method.DeclaringType.Name}.{method.Name} method has NO '{nameof(IBindingSourceMetadata)}' (Form, Body, Route, Query, Header) attributre");

                    if ((pi.IsDefined(typeof(FromBodyAttribute)) || pi.IsDefined(typeof(FromFormAttribute))) && !typeof(IFormFile).IsAssignableFrom(pi.ParameterType))
                        Assert.True(typeof(IApiArgs).IsAssignableFrom(pi.ParameterType),
                            $"Paramenter '{pi.Name} - {pi.ParameterType.Name}' in {method.DeclaringType.Name}.{method.Name} method is NOT implement {nameof(IApiArgs)}");
                }

                var methodReturnType = method.ReturnType;
                var methodReturnTypeInfo = methodReturnType.GetTypeInfo();

                if (methodReturnTypeInfo.IsGenericType && methodReturnTypeInfo.GetGenericTypeDefinition() == typeof(Task<>))
                    methodReturnType = methodReturnType.GenericTypeArguments[0];

                Assert.True(typeof(Core.Entities.Interfaces.IApiResult).IsAssignableFrom(methodReturnType) 
                    || typeof(IActionResult).IsAssignableFrom(methodReturnType),
                    $"'IApiResult' or 'IActionResult' is not implemented for return type '{methodReturnType.Name}' in {method.DeclaringType}.{method.Name}");
            }
        }

        [Fact(DisplayName = "SwaggerJson")]
        async void SwaggerJson()
        {
            var res = await ApiClient.HttpClient.GetAsync("../spec/v1/swagger.json");
            Assert.Equal(HttpStatusCode.OK, res.StatusCode);

            var jObject = JsonConvert.DeserializeObject<JObject>(await res.Content.ReadAsStringAsync());
            Assert.Equal("2.0", jObject["swagger"].Value<string>());
        }
    }
}
