// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Windows.Forms.CSharp.Analyzers.MissingPropertySerializationConfiguration;
using System.Windows.Forms.CSharp.CodeFixes.AddDesignerSerializationVisibility;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;

namespace System.Windows.Forms.Analyzers.Tests;

[ForceGC]
[SkipOnArchitecture(TestArchitectures.X86, "Analyzer tests hit OutOfMemoryException on x86 due to memory-mapped NuGet package extraction")]
public sealed class ControlPropertySerializationDiagnosticAnalyzerTest
{
    private const string GlobalUsingCode = """
        global using System.Drawing;
        global using System.Windows.Forms;
        """;

    private const string ProblematicCode = """
        namespace CSharpControls;

        public static class Program
        {
            public static void Main()
            {
                var control = new ScalableControl();

         // We deliberately format this weirdly, to make sure we only format code our code fix touches.
         control.ScaleFactor = 1.5f;
            control.ScaledSize = new SizeF(100, 100);
               control.ScaledLocation = new PointF(10, 10);
            }
        }

        // We are writing the fully-qualified name here to make sure, the Simplifier doesn't remove it,
        // since this is nothing our code fix touches.
        public class ScalableControl : System.Windows.Forms.Control
        {
            private SizeF _scaleSize = new SizeF(3, 14);

            /// <Summary>
            ///  Sets or gets the scaled size of some foo bar thing.
            /// </Summary>
            [System.ComponentModel.Description("Sets or gets the scaled size of some foo bar thing.")]
            public SizeF [|ScaledSize|]
            {
                get => _scaleSize;
                set => _scaleSize = value;
            }

            public float [|ScaleFactor|] { get; set; } = 1.0f;

            /// <Summary>
            ///  Sets or gets the scaled location of some foo bar thing.
            /// </Summary>
            public PointF [|ScaledLocation|] { get; set; }
        }

        """;

    private const string CorrectCode = """
        using System.ComponentModel;

        namespace CSharpControls;
        
        public static class Program
        {
            public static void Main()
            {
                var control = new ScalableControl();
        
         // We deliberately format this weirdly, to make sure we only format code our code fix touches.
         control.ScaleFactor = 1.5f;
            control.ScaledSize = new SizeF(100, 100);
               control.ScaledLocation = new PointF(10, 10);
            }
        }
        
        // We are writing the fully-qualified name here to make sure, the Simplifier doesn't remove it,
        // since this is nothing our code fix touches.
        public class ScalableControl : System.Windows.Forms.Control
        {
            private SizeF _scaleSize = new SizeF(3, 14);
        
            /// <Summary>
            ///  Sets or gets the scaled size of some foo bar thing.
            /// </Summary>
            [System.ComponentModel.Description("Sets or gets the scaled size of some foo bar thing.")]
            [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
            public SizeF ScaledSize
            {
                get => _scaleSize;
                set => _scaleSize = value;
            }
        
            [DefaultValue(1.0f)]
            public float ScaleFactor { get; set; } = 1.0f;
        
            /// <Summary>
            ///  Sets or gets the scaled location of some foo bar thing.
            /// </Summary>
            public PointF ScaledLocation { get; set; }

            private bool ShouldSerializeScaledLocation() => false;
        }
                
        """;

    private const string FixedCode = """
        using System.ComponentModel;

        namespace CSharpControls;
        
        public static class Program
        {
            public static void Main()
            {
                var control = new ScalableControl();
        
         // We deliberately format this weirdly, to make sure we only format code our code fix touches.
         control.ScaleFactor = 1.5f;
            control.ScaledSize = new SizeF(100, 100);
               control.ScaledLocation = new PointF(10, 10);
            }
        }
        
        // We are writing the fully-qualified name here to make sure, the Simplifier doesn't remove it,
        // since this is nothing our code fix touches.
        public class ScalableControl : System.Windows.Forms.Control
        {
            private SizeF _scaleSize = new SizeF(3, 14);
        
            /// <Summary>
            ///  Sets or gets the scaled size of some foo bar thing.
            /// </Summary>
            [System.ComponentModel.Description("Sets or gets the scaled size of some foo bar thing.")]
            [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
            public SizeF ScaledSize
            {
                get => _scaleSize;
                set => _scaleSize = value;
            }
        
            [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
            public float ScaleFactor { get; set; } = 1.0f;
        
            /// <Summary>
            ///  Sets or gets the scaled location of some foo bar thing.
            /// </Summary>
            [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
            public PointF ScaledLocation { get; set; }
        }
        
        """;

    // We are testing the analyzer with all versions of the .NET SDK from 6.0 on.
    public static IEnumerable<object[]> GetReferenceAssemblies()
    {
        yield return [ReferenceAssemblies.Net.Net60Windows];
        yield return [ReferenceAssemblies.Net.Net70Windows];
        yield return [ReferenceAssemblies.Net.Net80Windows];
        yield return [ReferenceAssemblies.Net.Net90Windows];
    }

    [Theory]
    [MemberData(nameof(GetReferenceAssemblies))]
    public async Task CS_ControlPropertySerializationConfigurationDiagnosticsEngage(ReferenceAssemblies referenceAssemblies)
    {
        var context = new CSharpAnalyzerTest
            <MissingPropertySerializationConfigurationAnalyzer,
             DefaultVerifier>
        {
            // Note: The ProblematicCode includes the expected Diagnostic's span in the areas
            // where the code is enclosed in limiting characters ("[|...|]"),
            // like `public SizeF [|ScaledSize|]`.
            TestCode = ProblematicCode,
            TestState =
                {
                    OutputKind = OutputKind.WindowsApplication,
                },
            ReferenceAssemblies = referenceAssemblies
        };

        context.TestState.Sources.Add(GlobalUsingCode);

        await context.RunAsync(TestContext.Current.CancellationToken);
    }

    [Theory]
    [MemberData(nameof(GetReferenceAssemblies))]
    public async Task CS_ControlPropertySerializationConfigurationDiagnosticPass(ReferenceAssemblies referenceAssemblies)
    {
        var context = new CSharpAnalyzerTest
            <MissingPropertySerializationConfigurationAnalyzer,
             DefaultVerifier>
        {
            TestCode = CorrectCode,
            TestState =
                {
                    OutputKind = OutputKind.WindowsApplication,
                },
            ReferenceAssemblies = referenceAssemblies
        };

        context.TestState.Sources.Add(GlobalUsingCode);

        await context.RunAsync(TestContext.Current.CancellationToken);
    }

    [Theory]
    [MemberData(nameof(GetReferenceAssemblies))]
    public async Task CS_AddDesignerSerializationVisibilityCodeFix(ReferenceAssemblies referenceAssemblies)
    {
        var context = new CSharpCodeFixTest
            <MissingPropertySerializationConfigurationAnalyzer,
             AddDesignerSerializationVisibilityCodeFixProvider,
             DefaultVerifier>
        {
            TestCode = ProblematicCode,
            FixedCode = FixedCode,
            TestState =
                {
                    OutputKind = OutputKind.WindowsApplication,
                    Sources = { GlobalUsingCode }
                },
            ReferenceAssemblies = referenceAssemblies,
            NumberOfFixAllIterations = 2,
            FixedState =
                {
                    Sources = { GlobalUsingCode }
                },
        };

        await context.RunAsync(TestContext.Current.CancellationToken);
    }
}
