// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0

using GFramework.Godot.SourceGenerators.Registration;
using GFramework.Godot.SourceGenerators.Tests.Core;

namespace GFramework.Godot.SourceGenerators.Tests.Registration;

[TestFixture]
public class AutoRegisterExportedCollectionsGeneratorTests
{
    private const string StandardAttributeDeclarations = """
                                                       [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
                                                       public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }

                                                       [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
                                                       public sealed class RegisterExportedCollectionAttribute : Attribute
                                                       {
                                                           public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
                                                       }
                                                       """;

    private const string MultiDeclarationAttributeDeclarations = """
                                                               [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
                                                               public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }

                                                               [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
                                                               public sealed class RegisterExportedCollectionAttribute : Attribute
                                                               {
                                                                   public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
                                                               }
                                                               """;

    [Test]
    public async Task Generates_Batch_Registration_Method_For_Annotated_Collections()
    {
        string source = CreateSource(
            """
                public sealed class IntRegistry
                {
                    public void Register(int value) { }
                }

                [AutoRegisterExportedCollections]
                public partial class Bootstrapper<TReference, TNotNull, TValue, TUnmanaged>
                    where TReference : class?
                    where TNotNull : notnull
                    where TValue : struct
                    where TUnmanaged : unmanaged
                {
                    private readonly IntRegistry? _registry = new();

                    [RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
                    public List<int>? Values { get; } = new();
                }
            """,
            nullableEnabled: true);

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                namespace TestApp;

                                partial class Bootstrapper<TReference, TNotNull, TValue, TUnmanaged>
                                    where TReference : class?
                                    where TNotNull : notnull
                                    where TValue : struct
                                    where TUnmanaged : unmanaged
                                {
                                    private void __RegisterExportedCollections_Generated()
                                    {
                                        if (this.Values is not null && this._registry is not null)
                                        {
                                            foreach (var __generatedItem in this.Values)
                                            {
                                                this._registry.Register(__generatedItem);
                                            }
                                        }
                                    }
                                }

                                """;

        await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
            source,
            ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
    }

    [Test]
    public async Task Reports_Diagnostic_When_Collection_Element_Type_Cannot_Be_Inferred()
    {
        const string source = """
                              using System;
                              using System.Collections;
                              using GFramework.Godot.SourceGenerators.Abstractions.UI;

                              namespace GFramework.Godot.SourceGenerators.Abstractions.UI
                              {
                                  [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
                                  public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }

                                  [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
                                  public sealed class RegisterExportedCollectionAttribute : Attribute
                                  {
                                      public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
                                  }
                              }

                              namespace TestApp
                              {
                                  public sealed class IntRegistry
                                  {
                                      public void Register(int value) { }
                                  }

                                  [AutoRegisterExportedCollections]
                                  public partial class Bootstrapper
                                  {
                                      private readonly IntRegistry _registry = new();

                                      [RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
                                      public IEnumerable {|#0:Values|} { get; } = new ArrayList();
                                  }
                              }
                              """;

        var test = new CSharpSourceGeneratorTest<AutoRegisterExportedCollectionsGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" }
        };

        test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoExport_005", DiagnosticSeverity.Error)
            .WithLocation(0)
            .WithArguments("Values"));

        await test.RunAsync().ConfigureAwait(false);
    }

    [Test]
    public async Task Generates_Batch_Registration_Method_When_Register_Method_Uses_Array_Parameter()
    {
        string source = CreateSource(
            """
                public sealed class ArrayRegistry
                {
                    public void Register(int[] value) { }
                }

                [AutoRegisterExportedCollections]
                public partial class Bootstrapper
                {
                    private readonly ArrayRegistry _registry = new();

                    [RegisterExportedCollection(nameof(_registry), nameof(ArrayRegistry.Register))]
                    public List<int[]> Values { get; } = new();
                }
            """,
            nullableEnabled: true);

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                namespace TestApp;

                                partial class Bootstrapper
                                {
                                    private void __RegisterExportedCollections_Generated()
                                    {
                                        if (this.Values is not null && this._registry is not null)
                                        {
                                            foreach (var __generatedItem in this.Values)
                                            {
                                                this._registry.Register(__generatedItem);
                                            }
                                        }
                                    }
                                }

                                """;

        await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
            source,
            ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
    }

    [Test]
    public async Task Generates_Batch_Registration_Method_When_Register_Method_Comes_From_Inherited_Interface()
    {
        string source = CreateSource(
            """
                public interface IKeyValue<TKey, TValue>
                {
                }

                public interface IRegistry<TKey, TValue>
                {
                    void Registry(IKeyValue<TKey, TValue> mapping);
                }

                public interface IAssetRegistry<TValue> : IRegistry<string, TValue>
                {
                }

                public sealed class IntConfig : IKeyValue<string, int>
                {
                }

                [AutoRegisterExportedCollections]
                public partial class Bootstrapper
                {
                    private readonly IAssetRegistry<int>? _registry = null;

                    [RegisterExportedCollection(nameof(_registry), "Registry")]
                    public List<IntConfig>? Values { get; } = new();
                }
            """,
            nullableEnabled: true);

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                namespace TestApp;

                                partial class Bootstrapper
                                {
                                    private void __RegisterExportedCollections_Generated()
                                    {
                                        if (this.Values is not null && this._registry is not null)
                                        {
                                            foreach (var __generatedItem in this.Values)
                                            {
                                                this._registry.Registry(__generatedItem);
                                            }
                                        }
                                    }
                                }

                                """;

        await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
            source,
            ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
    }

    [Test]
    public async Task Reports_Diagnostic_When_Register_Method_Is_Only_Explicitly_Implemented_Interface_Member()
    {
        const string source = """
                              using System;
                              using System.Collections.Generic;
                              using GFramework.Godot.SourceGenerators.Abstractions.UI;

                              namespace GFramework.Godot.SourceGenerators.Abstractions.UI
                              {
                                  [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
                                  public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }

                                  [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
                                  public sealed class RegisterExportedCollectionAttribute : Attribute
                                  {
                                      public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
                                  }
                              }

                              namespace TestApp
                              {
                                  public interface IRegistry
                                  {
                                      void Register(int value);
                                  }

                                  public sealed class ExplicitRegistry : IRegistry
                                  {
                                      void IRegistry.Register(int value) { }
                                  }

                                  [AutoRegisterExportedCollections]
                                  public partial class Bootstrapper
                                  {
                                      private readonly ExplicitRegistry _registry = new();

                                      [RegisterExportedCollection(nameof(_registry), "Register")]
                                      public List<int> {|#0:Values|} { get; } = new();
                                  }
                              }
                              """;

        var test = new CSharpSourceGeneratorTest<AutoRegisterExportedCollectionsGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" }
        };

        test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoExport_003", DiagnosticSeverity.Error)
            .WithLocation(0)
            .WithArguments("Register", "_registry", "Values"));

        await test.RunAsync().ConfigureAwait(false);
    }

    [Test]
    public async Task Generates_Batch_Registration_Method_When_Register_Method_Comes_From_Base_Class()
    {
        string source = CreateSource(
            """
                public class BaseRegistry
                {
                    public void Register(int value) { }
                }

                public sealed class DerivedRegistry : BaseRegistry
                {
                }

                [AutoRegisterExportedCollections]
                public partial class Bootstrapper
                {
                    private readonly DerivedRegistry? _registry = new();

                    [RegisterExportedCollection(nameof(_registry), nameof(BaseRegistry.Register))]
                    public List<int>? Values { get; } = new();
                }
            """,
            nullableEnabled: true);

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                namespace TestApp;

                                partial class Bootstrapper
                                {
                                    private void __RegisterExportedCollections_Generated()
                                    {
                                        if (this.Values is not null && this._registry is not null)
                                        {
                                            foreach (var __generatedItem in this.Values)
                                            {
                                                this._registry.Register(__generatedItem);
                                            }
                                        }
                                    }
                                }

                                """;

        await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
            source,
            ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
    }

    [Test]
    public async Task Generates_Batch_Registration_Method_When_Registry_Member_Comes_From_Base_Class()
    {
        string source = CreateSource(
            """
                public sealed class IntRegistry
                {
                    public void Register(int value) { }
                }

                public abstract class BootstrapperBase
                {
                    protected readonly IntRegistry? _registry = new();
                }

                [AutoRegisterExportedCollections]
                public partial class Bootstrapper : BootstrapperBase
                {
                    [RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
                    public List<int>? Values { get; } = new();
                }
            """,
            nullableEnabled: true);

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                namespace TestApp;

                                partial class Bootstrapper
                                {
                                    private void __RegisterExportedCollections_Generated()
                                    {
                                        if (this.Values is not null && this._registry is not null)
                                        {
                                            foreach (var __generatedItem in this.Values)
                                            {
                                                this._registry.Register(__generatedItem);
                                            }
                                        }
                                    }
                                }

                                """;

        await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
            source,
            ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
    }

    [Test]
    public async Task Reports_Diagnostic_When_Collection_Member_Is_Not_Instance_Readable()
    {
        string source = CreateSource(
            """
                public sealed class IntRegistry
                {
                    public void Register(int value) { }
                }

                [AutoRegisterExportedCollections]
                public partial class Bootstrapper
                {
                    private readonly IntRegistry _registry = new();

                    [RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
                    public static List<int> {|#0:StaticValues|} = new();

                    [RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
                    public static List<int> {|#1:StaticPropertyValues|} { get; } = new();

                    [RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
                    public List<int> {|#2:WriteOnlyValues|} { set { } }
                }
            """);

        await VerifyDiagnosticsAsync(
            source,
            skipGeneratedSourcesCheck: true,
            new DiagnosticResult("GF_AutoExport_006", DiagnosticSeverity.Error)
                .WithLocation(0)
                .WithArguments("StaticValues"),
            new DiagnosticResult("GF_AutoExport_006", DiagnosticSeverity.Error)
                .WithLocation(1)
                .WithArguments("StaticPropertyValues"),
            new DiagnosticResult("GF_AutoExport_006", DiagnosticSeverity.Error)
                .WithLocation(2)
                .WithArguments("WriteOnlyValues")).ConfigureAwait(false);
    }

    [Test]
    public async Task Reports_Diagnostic_When_Registry_Member_Is_Not_Instance_Readable()
    {
        const string source = """
                              using System;
                              using System.Collections.Generic;
                              using GFramework.Godot.SourceGenerators.Abstractions.UI;

                              namespace GFramework.Godot.SourceGenerators.Abstractions.UI
                              {
                                  [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
                                  public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }

                                  [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
                                  public sealed class RegisterExportedCollectionAttribute : Attribute
                                  {
                                      public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
                                  }
                              }

                              namespace TestApp
                              {
                                  public sealed class IntRegistry
                                  {
                                      public void Register(int value) { }
                                  }

                                  [AutoRegisterExportedCollections]
                                  public partial class Bootstrapper
                                  {
                                      private static readonly IntRegistry {|#0:_registry|} = new();

                                      [RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
                                      public List<int> Values { get; } = new();
                                  }
                              }
                              """;

        var test = new CSharpSourceGeneratorTest<AutoRegisterExportedCollectionsGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" },
            TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
        };

        test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoExport_007", DiagnosticSeverity.Error)
            .WithLocation(0)
            .WithArguments("_registry", "Values"));

        await test.RunAsync().ConfigureAwait(false);
    }

    [Test]
    public async Task Reports_Diagnostic_When_Register_Method_Is_Not_Accessible_From_Owner_Type()
    {
        const string source = """
                              using System;
                              using System.Collections.Generic;
                              using GFramework.Godot.SourceGenerators.Abstractions.UI;

                              namespace GFramework.Godot.SourceGenerators.Abstractions.UI
                              {
                                  [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
                                  public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }

                                  [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
                                  public sealed class RegisterExportedCollectionAttribute : Attribute
                                  {
                                      public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
                                  }
                              }

                              namespace TestApp
                              {
                                  public sealed class IntRegistry
                                  {
                                      private void Register(int value) { }
                                  }

                                  [AutoRegisterExportedCollections]
                                  public partial class Bootstrapper
                                  {
                                      private readonly IntRegistry _registry = new();

                                      [RegisterExportedCollection(nameof(_registry), "Register")]
                                      public List<int> {|#0:Values|} { get; } = new();
                                  }
                              }
                              """;

        var test = new CSharpSourceGeneratorTest<AutoRegisterExportedCollectionsGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" },
            TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
        };

        test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoExport_003", DiagnosticSeverity.Error)
            .WithLocation(0)
            .WithArguments("Register", "_registry", "Values"));

        await test.RunAsync().ConfigureAwait(false);
    }

    [Test]
    public async Task Reports_Diagnostic_When_RegisterExportedCollection_Attribute_Arguments_Are_Invalid()
    {
        const string source = """
                              using System;
                              using System.Collections.Generic;
                              using GFramework.Godot.SourceGenerators.Abstractions.UI;

                              namespace GFramework.Godot.SourceGenerators.Abstractions.UI
                              {
                                  [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
                                  public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }

                                  [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
                                  public sealed class RegisterExportedCollectionAttribute : Attribute
                                  {
                                      public RegisterExportedCollectionAttribute(string registryMemberName) { }
                                  }
                              }

                              namespace TestApp
                              {
                                  public sealed class IntRegistry
                                  {
                                      public void Register(int value) { }
                                  }

                                  [AutoRegisterExportedCollections]
                                  public partial class Bootstrapper
                                  {
                                      private readonly IntRegistry _registry = new();

                                      [{|#0:RegisterExportedCollection(nameof(_registry))|}]
                                      public List<int> Values { get; } = new();
                                  }
                              }
                              """;

        var test = new CSharpSourceGeneratorTest<AutoRegisterExportedCollectionsGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" },
            TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
        };

        test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoExport_008", DiagnosticSeverity.Error)
            .WithLocation(0)
            .WithArguments("Values"));

        await test.RunAsync().ConfigureAwait(false);
    }

    [Test]
    public async Task Generates_Only_One_Source_When_Multiple_Partial_Declarations_Are_Annotated()
    {
        string source = CreateSource(
            """
                public sealed class IntRegistry
                {
                    public void Register(int value) { }
                }

                [AutoRegisterExportedCollections]
                public partial class Bootstrapper
                {
                    private readonly IntRegistry? _registry = new();
                }

                [AutoRegisterExportedCollections]
                public partial class Bootstrapper
                {
                    [RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
                    public List<int>? Values { get; } = new();
                }
            """,
            nullableEnabled: true,
            allowMultipleDeclarations: true);

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                namespace TestApp;

                                partial class Bootstrapper
                                {
                                    private void __RegisterExportedCollections_Generated()
                                    {
                                        if (this.Values is not null && this._registry is not null)
                                        {
                                            foreach (var __generatedItem in this.Values)
                                            {
                                                this._registry.Register(__generatedItem);
                                            }
                                        }
                                    }
                                }

                                """;

        await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
            source,
            ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
    }

    private static string CreateSource(
        string applicationSource,
        bool nullableEnabled = false,
        bool allowMultipleDeclarations = false)
    {
        string nullableDirective = nullableEnabled ? "#nullable enable\n" : string.Empty;
        string attributeDeclarations = allowMultipleDeclarations
            ? MultiDeclarationAttributeDeclarations
            : StandardAttributeDeclarations;

        return $$"""
                 {{nullableDirective}}using System;
                 using System.Collections;
                 using System.Collections.Generic;
                 using GFramework.Godot.SourceGenerators.Abstractions.UI;

                 namespace GFramework.Godot.SourceGenerators.Abstractions.UI
                 {
                 {{attributeDeclarations}}
                 }

                 namespace TestApp
                 {
                 {{applicationSource}}
                 }
                 """;
    }

    private static async Task VerifyDiagnosticsAsync(
        string source,
        bool skipGeneratedSourcesCheck = false,
        params DiagnosticResult[] expectedDiagnostics)
    {
        var test = new CSharpSourceGeneratorTest<AutoRegisterExportedCollectionsGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" }
        };

        if (skipGeneratedSourcesCheck)
        {
            test.TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck;
        }

        foreach (DiagnosticResult expectedDiagnostic in expectedDiagnostics)
        {
            test.ExpectedDiagnostics.Add(expectedDiagnostic);
        }

        await test.RunAsync().ConfigureAwait(false);
    }
}
