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

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

namespace GFramework.Godot.SourceGenerators.Tests.Behavior;

[TestFixture]
public class AutoSceneGeneratorTests
{
    private const string AutoSceneAttributeWithKeyDeclaration = """
                                                                 [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
                                                                 public sealed class AutoSceneAttribute : Attribute
                                                                 {
                                                                     public AutoSceneAttribute(string key) { }
                                                                 }
                                                                 """;

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

    private const string NodeTypes = """
                                     public class Node { }
                                     public class Node2D : Node { }
                                     """;

    private const string SceneBehaviorInfrastructure = """
                                                       namespace GFramework.Game.Abstractions.Scene
                                                       {
                                                           public interface ISceneBehavior { }
                                                       }

                                                       namespace GFramework.Godot.Scene
                                                       {
                                                           using GFramework.Game.Abstractions.Scene;
                                                           using Godot;

                                                           public static class SceneBehaviorFactory
                                                           {
                                                               public static ISceneBehavior Create<T>(T owner, string key)
                                                                   where T : Node
                                                               {
                                                                   return null!;
                                                               }
                                                           }
                                                       }
                                                       """;

    [Test]
    public async Task Generates_Scene_Behavior_Boilerplate()
    {
        string source = CreateAutoSceneSource(
            AutoSceneAttributeWithKeyDeclaration,
            """
                [AutoScene("Gameplay")]
                public partial class GameplayRoot : Node2D
                {
                }
            """,
            includeBehaviorInfrastructure: true);

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

                                namespace TestApp;

                                partial class GameplayRoot
                                {
                                    private global::GFramework.Game.Abstractions.Scene.ISceneBehavior? __autoSceneBehavior_Generated;

                                    public static string SceneKeyStr => "Gameplay";

                                    public global::GFramework.Game.Abstractions.Scene.ISceneBehavior GetScene()
                                    {
                                        return __autoSceneBehavior_Generated ??= global::GFramework.Godot.Scene.SceneBehaviorFactory.Create(this, SceneKeyStr);
                                    }
                                }

                                """;

        await GeneratorTest<AutoSceneGenerator>.RunAsync(
            source,
            ("TestApp_GameplayRoot.AutoScene.g.cs", expected)).ConfigureAwait(false);
    }

    [Test]
    public async Task Reports_Diagnostic_When_AutoScene_Arguments_Are_Invalid()
    {
        string source = CreateAutoSceneSource(
            AutoSceneAttributeWithoutKeyDeclaration,
            """
                [{|#0:AutoScene|}]
                public partial class GameplayRoot : Node2D
                {
                }
            """);

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

        test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoBehavior_004", DiagnosticSeverity.Error)
            .WithLocation(0)
            .WithArguments("AutoSceneAttribute", "GameplayRoot", "a single string scene key argument"));

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

    [Test]
    public async Task Generates_Type_Constraints_For_Nullable_Reference_NotNull_And_Unmanaged_Parameters()
    {
        string source = CreateAutoSceneSource(
            AutoSceneAttributeWithKeyDeclaration,
            """
                [AutoScene("Gameplay")]
                public partial class GameplayRoot<TReference, TNotNull, TValue, TUnmanaged> : Node2D
                    where TReference : class?
                    where TNotNull : notnull
                    where TValue : struct
                    where TUnmanaged : unmanaged
                {
                }
            """,
            includeBehaviorInfrastructure: true,
            nullableEnabled: true);

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

                                namespace TestApp;

                                partial class GameplayRoot<TReference, TNotNull, TValue, TUnmanaged>
                                    where TReference : class?
                                    where TNotNull : notnull
                                    where TValue : struct
                                    where TUnmanaged : unmanaged
                                {
                                    private global::GFramework.Game.Abstractions.Scene.ISceneBehavior? __autoSceneBehavior_Generated;

                                    public static string SceneKeyStr => "Gameplay";

                                    public global::GFramework.Game.Abstractions.Scene.ISceneBehavior GetScene()
                                    {
                                        return __autoSceneBehavior_Generated ??= global::GFramework.Godot.Scene.SceneBehaviorFactory.Create(this, SceneKeyStr);
                                    }
                                }

                                """;

        await GeneratorTest<AutoSceneGenerator>.RunAsync(
            source,
            ("TestApp_GameplayRoot.AutoScene.g.cs", expected)).ConfigureAwait(false);
    }

    /// <summary>
    ///     验证宿主类型声明同名 <c>SceneKeyStr</c> 属性时，生成器会报告保留成员冲突并停止生成。
    /// </summary>
    [Test]
    public async Task Reports_Diagnostic_When_SceneKeyStr_Property_Name_Conflicts()
    {
        const string source = """
                              using System;
                              using GFramework.Godot.SourceGenerators.Abstractions.UI;
                              using Godot;

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

                              namespace Godot
                              {
                                  public class Node { }
                                  public class Node2D : Node { }
                              }

                              namespace TestApp
                              {
                                  [AutoScene("Gameplay")]
                                  public partial class GameplayRoot : Node2D
                                  {
                                      public static string {|#0:SceneKeyStr|} => "Conflict";
                                  }
                              }
                              """;

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

        test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error)
            .WithLocation(0)
            .WithArguments("GameplayRoot", "SceneKeyStr"));

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

    /// <summary>
    ///     验证宿主类型声明同名缓存字段时，生成器会报告保留成员冲突并停止生成。
    /// </summary>
    [Test]
    public async Task Reports_Diagnostic_When_Generated_Behavior_Field_Name_Conflicts()
    {
        const string source = """
                              using System;
                              using GFramework.Game.Abstractions.Scene;
                              using GFramework.Godot.SourceGenerators.Abstractions.UI;
                              using Godot;

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

                              namespace Godot
                              {
                                  public class Node { }
                                  public class Node2D : Node { }
                              }

                              namespace GFramework.Game.Abstractions.Scene
                              {
                                  public interface ISceneBehavior { }
                              }

                              namespace TestApp
                              {
                                  [AutoScene("Gameplay")]
                                  public partial class GameplayRoot : Node2D
                                  {
                                      private ISceneBehavior? {|#0:__autoSceneBehavior_Generated|};
                                  }
                              }
                              """;

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

        test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error)
            .WithLocation(0)
            .WithArguments("GameplayRoot", "__autoSceneBehavior_Generated"));

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

    private static string CreateAutoSceneSource(
        string attributeDeclaration,
        string testAppSource,
        bool includeBehaviorInfrastructure = false,
        bool nullableEnabled = false)
    {
        string nullableDirective = nullableEnabled ? "#nullable enable\n" : string.Empty;
        string infrastructure = includeBehaviorInfrastructure
            ? $"{Environment.NewLine}{Environment.NewLine}{SceneBehaviorInfrastructure}"
            : string.Empty;

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

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

                 namespace Godot
                 {
                 {{NodeTypes}}
                 }{{infrastructure}}

                 namespace TestApp
                 {
                 {{testAppSource}}
                 }
                 """;
    }
}
