// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Debugging;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Debugging
{
    [UseExportProvider]
    [Trait(Traits.Feature, Traits.Features.DebuggingLocationName)]
    public class LocationInfoGetterTests
    {
        private static async Task TestAsync(string markup, string expectedName, int expectedLineOffset, CSharpParseOptions parseOptions = null)
        {
            using var workspace = TestWorkspace.CreateCSharp(markup, parseOptions);

            var testDocument = workspace.Documents.Single();
            var position = testDocument.CursorPosition.Value;
            var locationInfo = await LocationInfoGetter.GetInfoAsync(
                workspace.CurrentSolution.Projects.Single().Documents.Single(),
                position,
                CancellationToken.None);

            Assert.Equal(expectedName, locationInfo.Name);
            Assert.Equal(expectedLineOffset, locationInfo.LineOffset);
        }

        [Fact]
        public async Task TestClass()
            => await TestAsync("class G$$oo { }", "Goo", 0);

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/527668"), WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/538415")]
        public async Task TestMethod()
        {
            await TestAsync(
                """
                class Class
                {
                    public static void Meth$$od()
                    {
                    }
                }
                """, "Class.Method()", 0);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/527668")]
        public async Task TestNamespace()
        {
            await TestAsync(
                """
                namespace Namespace
                {
                    class Class
                    {
                        void Method()
                        {
                        }$$
                    }
                }
                """, "Namespace.Class.Method()", 2);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/49000")]
        public async Task TestFileScopedNamespace()
        {
            // This test behavior is incorrect. This should be Namespace.Class.Method.
            // See the associated WorkItem for details.
            await TestAsync(
                """
                namespace Namespace;

                class Class
                {
                    void Method()
                    {
                    }$$
                }
                """, "Namespace.Class.Method()", 2);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/527668")]
        public async Task TestDottedNamespace()
        {
            await TestAsync(
                """
                namespace Namespace.Another
                {
                    class Class
                    {
                        void Method()
                        {
                        }$$
                    }
                }
                """, "Namespace.Another.Class.Method()", 2);
        }

        [Fact]
        public async Task TestNestedNamespace()
        {
            await TestAsync(
                """
                namespace Namespace
                {
                    namespace Another
                    {
                        class Class
                        {
                            void Method()
                            {
                            }$$
                        }
                    }
                }
                """, "Namespace.Another.Class.Method()", 2);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/527668")]
        public async Task TestNestedType()
        {
            await TestAsync(
                """
                class Outer
                {
                    class Inner
                    {
                        void Quux()
                        {$$
                        }
                    }
                }
                """, "Outer.Inner.Quux()", 1);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/527668")]
        public async Task TestPropertyGetter()
        {
            await TestAsync(
                """
                class Class
                {
                    string Property
                    {
                        get
                        {
                            return null;$$
                        }
                    }
                }
                """, "Class.Property", 4);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/527668")]
        public async Task TestPropertySetter()
        {
            await TestAsync(
                """
                class Class
                {
                    string Property
                    {
                        get
                        {
                            return null;
                        }

                        set
                        {
                            string s = $$value;
                        }
                    }
                }
                """, "Class.Property", 9);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/538415")]
        public async Task TestField()
        {
            await TestAsync(
                """
                class Class
                {
                    int fi$$eld;
                }
                """, "Class.field", 0);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543494")]
        public async Task TestLambdaInFieldInitializer()
        {
            await TestAsync(
                """
                class Class
                {
                    Action<int> a = b => { in$$t c; };
                }
                """, "Class.a", 0);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543494")]
        public async Task TestMultipleFields()
        {
            await TestAsync(
                """
                class Class
                {
                    int a1, a$$2;
                }
                """, "Class.a2", 0);
        }

        [Fact]
        public async Task TestConstructor()
        {
            await TestAsync(
                """
                class C1
                {
                    C1()
                    {

                    $$}
                }
                """, "C1.C1()", 3);
        }

        [Fact]
        public async Task TestDestructor()
        {
            await TestAsync(
                """
                class C1
                {
                    ~C1()
                    {
                    $$}
                }
                """, "C1.~C1()", 2);
        }

        [Fact]
        public async Task TestOperator()
        {
            await TestAsync(
                """
                namespace N1
                {
                    class C1
                    {
                        public static int operator +(C1 x, C1 y)
                        {
                            $$return 42;
                        }
                    }
                }
                """, "N1.C1.+(C1 x, C1 y)", 2); // Old implementation reports "operator +" (rather than "+")...
        }

        [Fact]
        public async Task TestConversionOperator()
        {
            await TestAsync(
                """
                namespace N1
                {
                    class C1
                    {
                        public static explicit operator N1.C2(N1.C1 x)
                        {
                            $$return null;
                        }
                    }
                    class C2
                    {
                    }
                }
                """, "N1.C1.N1.C2(N1.C1 x)", 2); // Old implementation reports "explicit operator N1.C2" (rather than "N1.C2")...
        }

        [Fact]
        public async Task TestEvent()
        {
            await TestAsync(
                """
                class C1
                {
                    delegate void D1();
                    event D1 e1$$;
                }
                """, "C1.e1", 0);
        }

        [Fact]
        public async Task TextExplicitInterfaceImplementation()
        {
            await TestAsync(
                """
                interface I1
                {
                    void M1();
                }
                class C1
                {
                    void I1.M1()
                    {
                    $$}
                }
                """, "C1.M1()", 2);
        }

        [Fact]
        public async Task TextIndexer()
        {
            await TestAsync(
                """
                class C1
                {
                    C1 this[int x]
                    {
                        get
                        {
                            $$return null;
                        }
                    }
                }
                """, "C1.this[int x]", 4);
        }

        [Fact]
        public async Task TestParamsParameter()
        {
            await TestAsync(
                """
                class C1
                {
                    void M1(params int[] x) { $$ }
                }
                """, "C1.M1(params int[] x)", 0);
        }

        [Fact]
        public async Task TestArglistParameter()
        {
            await TestAsync(
                """
                class C1
                {
                    void M1(__arglist) { $$ }
                }
                """, "C1.M1(__arglist)", 0); // Old implementation does not show "__arglist"...
        }

        [Fact]
        public async Task TestRefAndOutParameters()
        {
            await TestAsync(
                """
                class C1
                {
                    void M1( ref int x, out int y )
                    {
                        $$y = x;
                    }
                }
                """, "C1.M1( ref int x, out int y )", 2); // Old implementation did not show extra spaces around the parameters...
        }

        [Fact]
        public async Task TestOptionalParameters()
        {
            await TestAsync(
                """
                class C1
                {
                    void M1(int x =1)
                    {
                        $$y = x;
                    }
                }
                """, "C1.M1(int x =1)", 2);
        }

        [Fact]
        public async Task TestExtensionMethod()
        {
            await TestAsync(
                """
                static class C1
                {
                    static void M1(this int x)
                    {
                    }$$
                }
                """, "C1.M1(this int x)", 2);
        }

        [Fact]
        public async Task TestGenericType()
        {
            await TestAsync(
                """
                class C1<T, U>
                {
                    static void M1() { $$ }
                }
                """, "C1.M1()", 0);
        }

        [Fact]
        public async Task TestGenericMethod()
        {
            await TestAsync(
                """
                class C1<T, U>
                {
                    static void M1<V>() { $$ }
                }
                """, "C1.M1()", 0);
        }

        [Fact]
        public async Task TestGenericParameters()
        {
            await TestAsync(
                """
                class C1<T, U>
                {
                    static void M1<V>(C1<int, V> x, V y) { $$ }
                }
                """, "C1.M1(C1<int, V> x, V y)", 0);
        }

        [Fact]
        public async Task TestMissingNamespace()
        {
            await TestAsync(
                """
                {
                    class Class
                    {
                        int a1, a$$2;
                    }
                }
                """, "Class.a2", 0);
        }

        [Fact]
        public async Task TestMissingNamespaceName()
        {
            await TestAsync(
                """
                namespace
                {
                    class C1
                    {
                        int M1()
                        $${
                        }
                    }
                }
                """, "?.C1.M1()", 1);
        }

        [Fact]
        public async Task TestMissingClassName()
        {
            await TestAsync(
                """
                namespace N1
                    class 
                    {
                        int M1()
                        $${
                        }
                    }
                }
                """, "N1.M1()", 1); // Old implementation displayed "N1.?.M1", but we don't see a class declaration in the syntax tree...
        }

        [Fact]
        public async Task TestMissingMethodName()
        {
            await TestAsync(
                """
                namespace N1
                {
                    class C1
                    {
                        static void (ref int x)
                        {
                        $$}
                    }
                }
                """, "N1.C1", 4);
        }

        [Fact]
        public async Task TestMissingParameterList()
        {
            await TestAsync(
                """
                namespace N1
                {
                    class C1
                    {
                        static void M1
                        {
                        $$}
                    }
                }
                """, "N1.C1.M1", 2);
        }

        [Fact]
        public async Task TopLevelField()
        {
            await TestAsync(
                """
                $$int f1;
                """, "f1", 0, new CSharpParseOptions(kind: SourceCodeKind.Script));
        }

        [Fact]
        public async Task TopLevelMethod()
        {
            await TestAsync(
                """
                int M1(int x)
                {
                $$}
                """, "M1(int x)", 2, new CSharpParseOptions(kind: SourceCodeKind.Script));
        }

        [Fact]
        public async Task TopLevelStatement()
        {
            await TestAsync(
                """
                $$System.Console.WriteLine("Hello")
                """, null, 0, new CSharpParseOptions(kind: SourceCodeKind.Script));
        }
    }
}
