// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Build.Framework;

using Moq;

namespace Microsoft.Testing.Platform.MSBuild.UnitTests;

[TestClass]
public sealed class MSBuildTests
{
    private readonly Mock<IBuildEngine> _buildEngine = new();
    private readonly List<BuildErrorEventArgs> _errors = [];

    public MSBuildTests() =>
        _buildEngine.Setup(x => x.LogErrorEvent(It.IsAny<BuildErrorEventArgs>())).Callback<BuildErrorEventArgs>(e => _errors.Add(e));

    [TestMethod]
    public void Verify_Correct_Registration_Order_For_WellKnown_Extensions()
    {
        InMemoryFileSystem inMemoryFileSystem = new();
        TestingPlatformEntryPointTask testingPlatformEntryPoint = new(inMemoryFileSystem)
        {
            BuildEngine = _buildEngine.Object,
            TestingPlatformEntryPointSourcePath = new CustomTaskItem("obj/entryPointFile"),
            Language = new CustomTaskItem("C#"),
            RootNamespace = "SomeNamespace",
        };

        testingPlatformEntryPoint.Execute();

        string expectedSourceOrder = """
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by Microsoft.Testing.Platform.MSBuild
// </auto-generated>
//------------------------------------------------------------------------------

namespace SomeNamespace
{
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    internal static class MicrosoftTestingPlatformApplication
    {
        public static async global::System.Threading.Tasks.Task<int> RunAsync(string[] args)
        {
            global::Microsoft.Testing.Platform.Builder.ITestApplicationBuilder builder = await global::Microsoft.Testing.Platform.Builder.TestApplication.CreateBuilderAsync(args);
            global::SomeNamespace.SelfRegisteredExtensions.AddSelfRegisteredExtensions(builder, args);
            using (global::Microsoft.Testing.Platform.Builder.ITestApplication app = await builder.BuildAsync())
            {
                return await app.RunAsync();
            }
        }
    }

    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    internal sealed class MicrosoftTestingPlatformEntryPoint
    {
        public static global::System.Threading.Tasks.Task<int> Main(string[] args)
            => MicrosoftTestingPlatformApplication.RunAsync(args);
    }
}
""";

        Assert.AreEqual(expectedSourceOrder, inMemoryFileSystem.Files["obj/entryPointFile"]);
        Assert.IsEmpty(_errors);
    }

    [TestMethod]
    public void EntryPointTask_Generates_Reusable_Application_Helper_Without_Main()
    {
        InMemoryFileSystem inMemoryFileSystem = new();
        TestingPlatformEntryPointTask testingPlatformEntryPoint = new(inMemoryFileSystem)
        {
            BuildEngine = _buildEngine.Object,
            TestingPlatformEntryPointSourcePath = new CustomTaskItem("obj/applicationHelperFile"),
            Language = new CustomTaskItem("C#"),
            RootNamespace = "SomeNamespace",
            GenerateEntryPoint = false,
        };

        Assert.IsTrue(testingPlatformEntryPoint.Execute());

        string generatedSource = inMemoryFileSystem.Files["obj/applicationHelperFile"]!;
        Assert.Contains("internal static class MicrosoftTestingPlatformApplication", generatedSource);
        Assert.Contains("RunAsync(string[] args)", generatedSource);
        Assert.DoesNotContain("class MicrosoftTestingPlatformEntryPoint", generatedSource);
        Assert.DoesNotContain(" Main(", generatedSource);
        Assert.IsEmpty(_errors);
    }

    [TestMethod]
    public void EntryPointTask_Preserves_Global_FSharp_EntryPoint_Without_RootNamespace()
    {
        InMemoryFileSystem inMemoryFileSystem = new();
        TestingPlatformEntryPointTask testingPlatformEntryPoint = new(inMemoryFileSystem)
        {
            BuildEngine = _buildEngine.Object,
            TestingPlatformEntryPointSourcePath = new CustomTaskItem("obj/applicationHelperFile.fs"),
            Language = new CustomTaskItem("F#"),
        };

        Assert.IsTrue(testingPlatformEntryPoint.Execute());

        string generatedSource = inMemoryFileSystem.Files["obj/applicationHelperFile.fs"]!;
        Assert.Contains("module internal MicrosoftTestingPlatformApplication", generatedSource);
        Assert.Contains("module MicrosoftTestingPlatformEntryPoint", generatedSource);
        Assert.Contains("[<EntryPoint>]", generatedSource);
        Assert.DoesNotContain("namespace Microsoft.TestingPlatform", generatedSource);
        Assert.IsEmpty(_errors);
    }

    [TestMethod]
    public void SelfRegisteredExtensions_Deduplicates_Exact_Duplicate_BuilderHooks()
    {
        InMemoryFileSystem inMemoryFileSystem = new();
        TestingPlatformSelfRegisteredExtensions selfRegisteredExtensions = new(inMemoryFileSystem)
        {
            BuildEngine = _buildEngine.Object,
            SelfRegisteredExtensionsSourcePath = new CustomTaskItem("obj/selfRegisteredExtensionsFile"),
            Language = new CustomTaskItem("C#"),
            SelfRegisteredExtensionsBuilderHook =
            [
                new CustomTaskItem("hook").Add("DisplayName", "Hook").Add("TypeFullName", "Contoso.Hook"),
                new CustomTaskItem("hook").Add("DisplayName", "Hook").Add("TypeFullName", "Contoso.Hook"),
            ],
        };

        Assert.IsTrue(selfRegisteredExtensions.Execute());

        string generatedSource = inMemoryFileSystem.Files["obj/selfRegisteredExtensionsFile"]!;
        const string ExpectedHookCall = "global::Contoso.Hook.AddExtensions";
        int occurrences = (generatedSource.Length - generatedSource.Replace(ExpectedHookCall, string.Empty).Length) / ExpectedHookCall.Length;
        Assert.AreEqual(1, occurrences);
        Assert.IsEmpty(_errors);
    }

    [TestMethod]
    public void SelfRegisteredExtensions_Fails_For_Duplicate_BuilderHook_Ids_With_Different_Metadata()
    {
        InMemoryFileSystem inMemoryFileSystem = new();
        TestingPlatformSelfRegisteredExtensions selfRegisteredExtensions = new(inMemoryFileSystem)
        {
            BuildEngine = _buildEngine.Object,
            SelfRegisteredExtensionsSourcePath = new CustomTaskItem("obj/selfRegisteredExtensionsFile"),
            Language = new CustomTaskItem("C#"),
            SelfRegisteredExtensionsBuilderHook =
            [
                new CustomTaskItem("hook").Add("DisplayName", "Hook").Add("TypeFullName", "Contoso.Hook"),
                new CustomTaskItem("hook").Add("DisplayName", "OtherHook").Add("TypeFullName", "Contoso.OtherHook"),
            ],
        };

        Assert.IsFalse(selfRegisteredExtensions.Execute());

        Assert.IsFalse(inMemoryFileSystem.Files.ContainsKey("obj/selfRegisteredExtensionsFile"));
        Assert.HasCount(1, _errors);
        Assert.Contains("Duplicate 'TestingPlatformBuilderHook' item with Include 'hook' has conflicting metadata.", _errors[0].Message ?? string.Empty);
    }

    private sealed class InMemoryFileSystem : IFileSystem
    {
        public Dictionary<string, string?> Files { get; } = [];

        public void CopyFile(string source, string destination) => throw new NotImplementedException();

        public void CreateDirectory(string directory) => throw new NotImplementedException();

        public Stream CreateNew(string path) => throw new NotImplementedException();

        public bool Exist(string path) => Files.ContainsKey(path);

        public void WriteAllText(string path, string? contents) => Files.Add(path, contents);
    }

    private sealed class CustomTaskItem : ITaskItem
    {
        private readonly Dictionary<string, string> _keyValuePairs = [];

        public CustomTaskItem(string itemSpec) => ItemSpec = itemSpec;

        public CustomTaskItem Add(string key, string value)
        {
            _keyValuePairs[key] = value;
            return this;
        }

        public string ItemSpec { get; set; }

        public ICollection MetadataNames => throw new NotImplementedException();

        public int MetadataCount => _keyValuePairs.Count;

        public IDictionary CloneCustomMetadata() => throw new NotImplementedException();

        public void CopyMetadataTo(ITaskItem destinationItem) => throw new NotImplementedException();

        public string? GetMetadata(string metadataName) => _keyValuePairs.TryGetValue(metadataName, out string? value) ? value : null;

        public void RemoveMetadata(string metadataName) => throw new NotImplementedException();

        public void SetMetadata(string metadataName, string metadataValue) => throw new NotImplementedException();
    }
}
