using System.Reflection;
using System.Text;
using eSuite.API.security;
using eSuite.API.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using NUnit.Framework;

namespace eSuite.API.UnitTests.Controllers.MiscellaniousUnitTests;

[TestFixture]
public class ControllerUnitTests
{
    private static IEnumerable<Type> GetAllControllers()
    {
        var referencedAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
        var esuiteAPi = referencedAssemblies.Single(assembly => assembly.Name == "eSuite.API");
        var assembly = Assembly.Load(esuiteAPi);

        var controllers = assembly
            .GetTypes()
            .Where(type => typeof(ControllerBase).IsAssignableFrom(type) && !type.IsAbstract);
        return controllers;
    }

    private static CustomAttributeData? FindAttribute(MethodInfo publicMethod, Type type)
    {
        return publicMethod.CustomAttributes.FirstOrDefault(customAttribute => type.IsAssignableFrom(customAttribute.AttributeType));
    }

    private static bool ImplementsParameterByType(MethodInfo publicMethod, Type type)
    {
        return publicMethod.GetParameters().Any(parameter => type.IsAssignableFrom(parameter.ParameterType));
    }

    [Test]
    public void Controller_Implemented_IsInheritedFromESuiteControllerBase()
    {
        //Arrange
        var controllers = GetAllControllers();

        Assert.That(controllers.Count(), Is.GreaterThan(0));

        //Act
        foreach (var controller in controllers)
            if (!(typeof(ESuiteControllerBase).IsAssignableFrom(controller) || typeof(ESuiteController).IsAssignableFrom(controller)))
                //Assert
                Assert.Fail($"{controller.FullName} is not {typeof(ESuiteControllerBase)} or {typeof(ESuiteController)}");
    }

    [Test]
    public void Controller_EveryMethod_ImplementsCancellationToken()
    {
        //Arrange
        var controllers = GetAllControllers();

        foreach (var controller in controllers)
        {
            var publicMethods = controller.GetMethods().Where(method => method.IsPublic).ToArray();

            //Act
            foreach (var publicMethod in publicMethods)
            {
                var attribute = FindAttribute(publicMethod, typeof(HttpMethodAttribute));
                if (attribute == null)
                    continue;

                if (!ImplementsParameterByType(publicMethod, typeof(CancellationToken)))
                    //Assert
                    Assert.Fail($"{controller.Name}.{publicMethod.Name} does not have a {typeof(CancellationToken)} parameter");
            }

            //Assert
            Assert.That(publicMethods.Length, Is.GreaterThan(0));
        }
    }

    [Test]
    public void Controller_EveryMethod_HasAccessKeyAttribute()
    {
        //Arrange
        var errors = new List<string>();

        var controllers = GetAllControllers();

        foreach (var controller in controllers)
        {
            var publicMethods = controller.GetMethods().Where(method => method.IsPublic).ToArray();

            //Act
            foreach (var publicMethod in publicMethods)
            {
                var attribute = FindAttribute(publicMethod, typeof(HttpMethodAttribute));
                if (attribute == null)
                    continue;

                var accessKeyAttribute = FindAttribute(publicMethod, typeof(AccessKeyAttribute));
                if (accessKeyAttribute == null)
                {
                    var allowAnonymousAttribute = FindAttribute(publicMethod, typeof(AllowAnonymousAttribute));
                    if (allowAnonymousAttribute != null)
                        continue;
                    
                    errors.Add($"{controller.Name}.{publicMethod.Name} does not have an AccessKeyAttribute");
                }
            }

            //Assert
            Assert.That(publicMethods.Count, Is.GreaterThan(0));
        }

        Assert.Multiple(() =>
        {
            if (errors.Count > 0)
            {
                var sb = new StringBuilder();
                foreach (var error in errors)
                {
                    sb.AppendLine(error);
                }
                    
                Assert.Fail(sb.ToString());
            }
        });
    }
}