using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using e_suite.Database.Audit.Attributes;
using Microsoft.EntityFrameworkCore;
using NUnit.Framework;

namespace e_suite.Database.Core.UnitTests;

[TestFixture]
public class TableDefinitionUnitTests
{
    protected List<Type> Tables { get; set; } = [];

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

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

    private static List<string> CompileAcceptableTableNames(string tableName)
    {
        var result = new List<string>();
        
        var lastCharactor = tableName[^1..];
        var lastTwoCharactors = tableName[^2..];

        if (lastTwoCharactors.Equals("ey", StringComparison.InvariantCultureIgnoreCase))
        {
            result.Add(tableName + "s");
        }
        else if (lastCharactor.Equals("y", StringComparison.InvariantCultureIgnoreCase))
        {
            result.Add(string.Concat(tableName.AsSpan(0, tableName.Length - 1), "ies"));
        }
        else if (lastCharactor.Equals("s", StringComparison.InvariantCultureIgnoreCase))
        {
            result.Add(tableName);
        }
        else
        {
            result.Add(tableName + "s");
        }

        return result;
    }

    [SetUp]
    public void Setup()
    {
        Tables.Clear();
        var databaseContextClass = typeof(EsuiteDatabaseDbContext);

        var properties = databaseContextClass.GetProperties();
        foreach (var property in properties)
        {
            if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
            {
                Tables.Add(property.PropertyType.GenericTypeArguments[0]);
            }
        }
    }
    

    [Test]
    public void Table_PropertyHasAuditParentAttribute_AlsoHasForeignKey()
    {
        //Arrange
        //Act
        foreach (var table in Tables)
        {
            //Need to check all the properties of the table to see if it contains the AuditParent Custom property
            var properties = table.GetProperties();

            foreach (var property in properties)
            {
                if (FindAttribute(property, typeof(AuditParentAttribute)) != null)
                {
                    //Assert
                    if (FindAttribute(property, typeof(ForeignKeyAttribute)) != null)
                        Assert.Pass($"Property {property.Name} Has both AuditParentAttribute And ForeignKey attribute");
                    else
                        Assert.Fail($"Property {property.Name} Has AuditParentAttribute but is missing the ForeignKey attribute");
                }
            }
        }
    }

    [Test]
    public void Table_TableName_IsSetConsistently()
    {
        //Arrange
        //Act
        foreach (var table in Tables)
        {
            //Need to check all the properties of the table to see if it contains the AuditParent Custom property
            var tableAttribute = FindAttribute(table, typeof(TableAttribute));

            //Assert
            if (tableAttribute == null)
                Assert.Fail($"Table \"{table.Name}\" Missing the TableNameAttribute");

            var acceptableTableNames = CompileAcceptableTableNames(table.Name);

            var tableName = tableAttribute?.ConstructorArguments[0].Value?.ToString() ?? string.Empty;
            
            if (!acceptableTableNames.Contains(tableName))
                Assert.Fail($"Table name \"{tableName}\" for class \"{table.Name}\" does not conform to the naming standards");

            var schemaName = tableAttribute!.NamedArguments.Single( x=> x.MemberName == "Schema");
            var tableNamespace = table.Namespace?.Split(".");
            if (tableNamespace != null && tableNamespace.Last() != schemaName.TypedValue.Value?.ToString())
                Assert.Fail($"Schema name for \"{table.Name}\" set incorrectly is currently \"{schemaName.TypedValue.Value}\" but should be \"{tableNamespace}\"");
        }
    }

    [Test]
    public void Table_AuditedTables_HasLastUpdated()
    {
        //Arrange
        //Act
        foreach (var table in Tables)
        {
            //Assert
            var tableAttribute = FindAttribute(table, typeof(TableAttribute));
            if (tableAttribute == null)
                continue;

                //Need to check all the properties of the table to see if it contains the AuditParent Custom property
            var noAuditAttribute = FindAttribute(table, typeof(NoAuditAttribute));
            if (noAuditAttribute != null)
                continue;

            var foundLastUpdated = false;
            var properties = table.GetProperties();
            foreach (var property in properties)
            {
                if (FindAttribute(property, typeof(AuditLastUpdatedAttribute)) != null)
                {
                    foundLastUpdated = true;
                    break;
                }
            }

            if (!foundLastUpdated)
                Assert.Fail($"Table {table.Name} Missing {nameof(AuditLastUpdatedAttribute)}");
        }
    }

    [Test]
    public void Table_ForeignKeysAreVirtual()
    {
        //Arrange
        //Act
        foreach (var table in Tables)
        {
            var properties = table.GetProperties();
            foreach (var property in properties)
            {
                if (FindAttribute(property, typeof(ForeignKeyAttribute)) != null)
                {
                    //Assert
                    if (!property.GetAccessors().Any( x=> x.IsVirtual))
                        Assert.Fail($"Table {table.Name}.{property.Name} is not virtual");
                }
            }
        }
    }
}