extern alias SerializationGenerator;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using NUnit.Framework;
using Robust.Analyzers.Generators;

namespace Robust.Analyzers.Tests;

[TestFixture]
[TestOf(typeof(HasDependenciesGenerator))]
[Parallelizable(ParallelScope.All)]
public sealed class HasDependenciesGeneratorTest
{
    [Test]
    public void TestBasic()
    {
        var result = RunGenerator("""
            using Robust.Shared.IoC;

            public sealed partial class Foobar
            {
                [Dependency]
                public string Foo;
            }
            """);

        ExpectNoDiagnostics(result);
        ExpectSource(
            result,
            """
            // <auto-generated />

            [global::Robust.Shared.IoC.HasDependenciesGeneratedAttribute]
            public partial class Foobar : global::Robust.Shared.IoC.IHasDependencies
            {
                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                global::System.Type[] global::Robust.Shared.IoC.IHasDependencies.GetDependencyTypes()
                {
                    return new global::System.Type[]
                    {
                        typeof(global::string)
                    };
                }

                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                void global::Robust.Shared.IoC.IHasDependencies.Inject(global::System.ReadOnlySpan<object> instances)
                {
                    Foo = (global::string)instances[0];
                }
            }

            """);
    }

    [Test]
    public void TestInheritGeneric()
    {
        var result = RunGenerator("""
            using Robust.Shared.IoC;

            public partial class Foo<T>
            {
                [Dependency] string _x = null!;
            }

            public sealed partial class Bar : Foo<int>
            {
                [Dependency]
                public string _heck = null!;
            }
            """);

        ExpectNoDiagnostics(result);
        Assert.That(result.GeneratedSources, Has.Length.EqualTo(2));

        ExpectNamedSource(
            result,
            "Foo`1.g.cs",
            """
            // <auto-generated />

            [global::Robust.Shared.IoC.HasDependenciesGeneratedAttribute]
            public partial class Foo<T> : global::Robust.Shared.IoC.IHasDependencies
            {
                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                global::System.Type[] global::Robust.Shared.IoC.IHasDependencies.GetDependencyTypes()
                {
                    return GetDependencyTypesImpl();
                }

                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                void global::Robust.Shared.IoC.IHasDependencies.Inject(global::System.ReadOnlySpan<object> instances)
                {
                    InjectImpl(instances);
                }

                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                protected virtual global::System.Type[] GetDependencyTypesImpl()
                {
                    return new global::System.Type[]
                    {
                        typeof(global::string)
                    };
                }

                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                protected virtual void InjectImpl(global::System.ReadOnlySpan<object> instances)
                {
                    _x = (global::string)instances[0];
                }
            }

            """);

        ExpectNamedSource(
            result,
            "Bar.g.cs",
            """
            // <auto-generated />

            [global::Robust.Shared.IoC.HasDependenciesGeneratedAttribute]
            public partial class Bar
            {
                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                protected override global::System.Type[] GetDependencyTypesImpl()
                {
                    var baseTypes = base.GetDependencyTypesImpl();
                    var types = new global::System.Type[baseTypes.Length + 1];

                    types[0] = typeof(global::string);

                    global::System.Array.Copy(baseTypes, 0, types, 1, baseTypes.Length);

                    return types;
                }

                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                protected override void InjectImpl(global::System.ReadOnlySpan<object> instances)
                {
                    _heck = (global::string)instances[0];

                    base.InjectImpl(instances.Slice(1));
                }
            }

            """);
    }

    [Test]
    public void TestGenericInherit()
    {
        var result = RunGenerator("""
            using Robust.Shared.IoC;

            public partial class Foo
            {
                [Dependency] string _x = null!;
            }

            public sealed partial class Bar<T> : Foo
            {
                [Dependency]
                public string _heck = null!;
            }
            """);

        ExpectNoDiagnostics(result);
        Assert.That(result.GeneratedSources, Has.Length.EqualTo(2));

        ExpectNamedSource(
            result,
            "Foo.g.cs",
            """
            // <auto-generated />

            [global::Robust.Shared.IoC.HasDependenciesGeneratedAttribute]
            public partial class Foo : global::Robust.Shared.IoC.IHasDependencies
            {
                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                global::System.Type[] global::Robust.Shared.IoC.IHasDependencies.GetDependencyTypes()
                {
                    return GetDependencyTypesImpl();
                }

                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                void global::Robust.Shared.IoC.IHasDependencies.Inject(global::System.ReadOnlySpan<object> instances)
                {
                    InjectImpl(instances);
                }

                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                protected virtual global::System.Type[] GetDependencyTypesImpl()
                {
                    return new global::System.Type[]
                    {
                        typeof(global::string)
                    };
                }

                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                protected virtual void InjectImpl(global::System.ReadOnlySpan<object> instances)
                {
                    _x = (global::string)instances[0];
                }
            }

            """);

        ExpectNamedSource(
            result,
            "Bar`1.g.cs",
            """
            // <auto-generated />

            [global::Robust.Shared.IoC.HasDependenciesGeneratedAttribute]
            public partial class Bar<T>
            {
                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                protected override global::System.Type[] GetDependencyTypesImpl()
                {
                    var baseTypes = base.GetDependencyTypesImpl();
                    var types = new global::System.Type[baseTypes.Length + 1];

                    types[0] = typeof(global::string);

                    global::System.Array.Copy(baseTypes, 0, types, 1, baseTypes.Length);

                    return types;
                }

                [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                protected override void InjectImpl(global::System.ReadOnlySpan<object> instances)
                {
                    _heck = (global::string)instances[0];

                    base.InjectImpl(instances.Slice(1));
                }
            }

            """);
    }


    [Test]
    public void TestReadOnly()
    {
        var result = RunGenerator("""
            using Robust.Shared.IoC;

            public sealed partial class Foobar
            {
                [Dependency]
                public readonly string Foo;
            }
            """);

        ExpectNoDiagnostics(result);
        ExpectNoSource(result);
    }


    [Test]
    public void TestNotPartial()
    {
        var result = RunGenerator("""
            using Robust.Shared.IoC;

            public sealed class Foobar
            {
                [Dependency]
                public string Foo;
            }
            """);

        ExpectNoDiagnostics(result);
        ExpectNoSource(result);
    }

    [Test]
    public void TestNested()
    {
        var result = RunGenerator("""
            using Robust.Shared.IoC;

            public sealed partial class Real
            {
                public sealed partial class Foobar
                {
                    [Dependency]
                    public string Foo;
                }
            }
            """);

        ExpectNoDiagnostics(result);
        ExpectSource(
            result,
            """
            // <auto-generated />

            public partial class Real
            {
                [global::Robust.Shared.IoC.HasDependenciesGeneratedAttribute]
                public partial class Foobar : global::Robust.Shared.IoC.IHasDependencies
                {
                    [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                    global::System.Type[] global::Robust.Shared.IoC.IHasDependencies.GetDependencyTypes()
                    {
                        return new global::System.Type[]
                        {
                            typeof(global::string)
                        };
                    }

                    [global::Robust.Shared.Analyzers.RobustAutoGenerated]
                    void global::Robust.Shared.IoC.IHasDependencies.Inject(global::System.ReadOnlySpan<object> instances)
                    {
                        Foo = (global::string)instances[0];
                    }
                }
            }

            """);
    }

    private static void ExpectSource(GeneratorRunResult result, string expected)
    {
        Assert.That(result.GeneratedSources, Has.Length.EqualTo(1));

        var source = result.GeneratedSources[0];

        Assert.That(source.SourceText.ToString().ReplaceLineEndings(), Is.EqualTo(expected.ReplaceLineEndings()));
    }

    private static void ExpectNamedSource(GeneratorRunResult result, string name, string expected)
    {
        var source = result.GeneratedSources.Single(s => s.HintName == name);

        Assert.That(source.SourceText.ToString().ReplaceLineEndings(), Is.EqualTo(expected.ReplaceLineEndings()));
    }

    private static void ExpectNoSource(GeneratorRunResult result)
    {
        Assert.That(result.GeneratedSources, Is.Empty);
    }

    private static void ExpectNoDiagnostics(GeneratorRunResult result)
    {
        Assert.That(result.Diagnostics, Is.Empty);
    }

    private static void ExpectDiagnostics(GeneratorRunResult result, (string code, LinePositionSpan span)[] diagnostics)
    {
        Assert.Multiple(() =>
        {
            Assert.That(result.Diagnostics, Has.Length.EqualTo(diagnostics.Length));
            foreach (var (code, span) in diagnostics)
            {
                Assert.That(
                    result.Diagnostics.Any(x => x.Id == code && x.Location.GetLineSpan().Span == span),
                    $"Expected diagnostic with code {code} and location {span}");
            }
        });
    }

    private static GeneratorRunResult RunGenerator(string source)
    {
        var compilation = (Compilation)CSharpCompilation.Create("compilation",
            [
                CSharpSyntaxTree.ParseText(source, path: "Source.cs"),
                ..TestHelper.GetEmbeddedSyntaxTrees(
                    "Robust.Shared.IoC.DependencyAttribute.cs",
                    "Robust.Shared.IoC.IHasDependencies.cs"),
            ],
            new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
            new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

        var generator = new HasDependenciesGenerator();
        GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
        driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out _);
        var result = driver.GetRunResult();

        return result.Results[0];
    }
}
