using System.Reflection;
using NetArchTest.Rules;
using NUnit.Framework;
using Shouldly;

namespace ErsatzTV.Architecture.Tests;

// Enforces the dependency direction of the solution's layers (see docs/ci-cd.md / contributors guide).
// NetArchTest analyses the compiled assemblies, so a "just import it here" violation fails the build
// instead of eroding the architecture over time. Rules below reflect the intended layering and the
// current project-reference graph:
//   FFmpeg (lowest) <- Core <- Infrastructure <- {Infrastructure.Sqlite, Infrastructure.MySql}
//   Core <- Application;  everything <- ErsatzTV (host / composition root)
[TestFixture]
public class LayeringTests
{
    private static Assembly Core => Assembly.Load("ErsatzTV.Core");
    private static Assembly FFmpeg => Assembly.Load("ErsatzTV.FFmpeg");
    private static Assembly Application => Assembly.Load("ErsatzTV.Application");
    private static Assembly Infrastructure => Assembly.Load("ErsatzTV.Infrastructure");

    [Test]
    public void Core_should_not_depend_on_outer_layers()
    {
        TestResult result = Types.InAssembly(Core)
            .Should()
            .NotHaveDependencyOnAny(
                "ErsatzTV.Application",
                "ErsatzTV.Infrastructure",
                "ErsatzTV.Scanner")
            .GetResult();

        result.IsSuccessful.ShouldBeTrue(Describe(result));
    }

    [Test]
    public void Core_should_not_depend_on_a_database_or_orm()
    {
        TestResult result = Types.InAssembly(Core)
            .Should()
            .NotHaveDependencyOnAny(
                "Microsoft.EntityFrameworkCore",
                "Pomelo.EntityFrameworkCore",
                "Microsoft.Data.Sqlite",
                "Dapper")
            .GetResult();

        result.IsSuccessful.ShouldBeTrue(Describe(result));
    }

    [Test]
    public void FFmpeg_should_not_depend_on_other_layers()
    {
        TestResult result = Types.InAssembly(FFmpeg)
            .Should()
            .NotHaveDependencyOnAny(
                "ErsatzTV.Core",
                "ErsatzTV.Application",
                "ErsatzTV.Infrastructure",
                "ErsatzTV.Scanner")
            .GetResult();

        result.IsSuccessful.ShouldBeTrue(Describe(result));
    }

    [Test]
    public void Application_should_not_depend_on_concrete_database_providers()
    {
        TestResult result = Types.InAssembly(Application)
            .Should()
            .NotHaveDependencyOnAny(
                "ErsatzTV.Infrastructure.Sqlite",
                "ErsatzTV.Infrastructure.MySql")
            .GetResult();

        result.IsSuccessful.ShouldBeTrue(Describe(result));
    }

    [Test]
    public void Infrastructure_should_not_depend_on_application_or_concrete_providers()
    {
        TestResult result = Types.InAssembly(Infrastructure)
            .Should()
            .NotHaveDependencyOnAny(
                "ErsatzTV.Application",
                "ErsatzTV.Infrastructure.Sqlite",
                "ErsatzTV.Infrastructure.MySql")
            .GetResult();

        result.IsSuccessful.ShouldBeTrue(Describe(result));
    }

    private static string Describe(TestResult result)
    {
        if (result.IsSuccessful)
        {
            return string.Empty;
        }

        IEnumerable<string> names = result.FailingTypes?.Select(t => t.FullName ?? t.Name)
                                    ?? Enumerable.Empty<string>();
        return "Layering violation — offending types:\n  " + string.Join("\n  ", names);
    }
}
