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

#if NETFRAMEWORK
using System.Security.Policy;

using AwesomeAssertions;

using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;

using Moq;

using TestFramework.ForTestingMSTest;

namespace MSTestAdapter.PlatformServices.UnitTests;

public class DesktopTestSourceHostTests : TestContainer
{
    public void GetResolutionPathsShouldAddPublicAndPrivateAssemblyPath()
    {
        // Setup
        TestSourceHost sut = new(null!, null);

        // Execute
        // It should return public and private path if it is not running in portable mode.
        List<string> result = sut.GetResolutionPaths("DummyAssembly.dll", isPortableMode: false);

        // Assert
        if (!string.IsNullOrWhiteSpace(VSInstallationUtilities.PathToPublicAssemblies))
        {
            result.Should().Contain(VSInstallationUtilities.PathToPublicAssemblies!);
        }

        if (!string.IsNullOrWhiteSpace(VSInstallationUtilities.PathToPrivateAssemblies))
        {
            result.Should().Contain(VSInstallationUtilities.PathToPrivateAssemblies!);
        }
    }

    public void GetResolutionPathsShouldNotAddPublicAndPrivateAssemblyPathInPortableMode()
    {
        // Setup
        TestSourceHost sut = new(null!, null);

        // Execute
        // It should not return public and private path if it is running in portable mode.
        List<string> result = sut.GetResolutionPaths("DummyAssembly.dll", isPortableMode: true);

        // Assert
        result.Should().NotContain(VSInstallationUtilities.PathToPublicAssemblies!);
        result.Should().NotContain(VSInstallationUtilities.PathToPrivateAssemblies!);
    }

    public void GetResolutionPathsShouldAddAdapterFolderPath()
    {
        // Setup
        TestSourceHost sut = new(null!, null);

        // Execute
        List<string> result = sut.GetResolutionPaths("DummyAssembly.dll", isPortableMode: false);

        // Assert
        result.Should().NotContain(typeof(TestSourceHost).Assembly.Location);
    }

    public void GetResolutionPathsShouldAddTestPlatformFolderPath()
    {
        // Setup
        TestSourceHost sut = new(null!, null);

        // Execute
        List<string> result = sut.GetResolutionPaths("DummyAssembly.dll", isPortableMode: false);

        // Assert
        result.Should().NotContain(typeof(AssemblyHelper).Assembly.Location);
    }

    public void CreateInstanceForTypeShouldCreateTheTypeInANewAppDomain()
    {
        // Setup
        DummyClass dummyClass = new();
        int currentAppDomainId = dummyClass.AppDomainId;

        TestSourceHost sut = new(Assembly.GetExecutingAssembly().Location, null);
        sut.SetupHost();

        // Execute
        int newAppDomainId = currentAppDomainId + 10;  // not equal to currentAppDomainId
        if (sut.CreateInstanceForType(typeof(DummyClass), null) is DummyClass expectedObject)
        {
            newAppDomainId = expectedObject.AppDomainId;
        }

        // Assert
        currentAppDomainId.Should().NotBe(newAppDomainId);
    }

    public void SetupHostShouldSetChildDomainsAppBaseToTestSourceLocation()
    {
        // Arrange
        _ = new DummyClass();

        string location = typeof(TestSourceHost).Assembly.Location;
        Mock<TestSourceHost> sourceHost = new(location, null!) { CallBase = true };

        try
        {
            // Act
            sourceHost.Object.SetupHost();
            var expectedObject = sourceHost.Object.CreateInstanceForType(typeof(DummyClass), null) as DummyClass;

            // Assert
            expectedObject?.AppDomainAppBase.Should().Be(Path.GetDirectoryName(typeof(DesktopTestSourceHostTests).Assembly.Location));
        }
        finally
        {
            sourceHost.Object.Dispose();
        }
    }

    public void SetupHostShouldHaveParentDomainsAppBaseSetToTestSourceLocation()
    {
        // Arrange
        DummyClass dummyClass = new();
        string runSettingsXml =
            """
            <RunSettings>
              <RunConfiguration>
                <DisableAppDomain>False</DisableAppDomain>
              </RunConfiguration>
            </RunSettings>
            """;

        string location = typeof(TestSourceHost).Assembly.Location;
        TestSourceHost sourceHost = new(location, runSettingsXml);

        try
        {
            // Act
            sourceHost.SetupHost();
            var expectedObject = sourceHost.CreateInstanceForType(typeof(DummyClass), null) as DummyClass;

            // Assert
            expectedObject?.AppDomainAppBase.Should().Be(Path.GetDirectoryName(typeof(DesktopTestSourceHostTests).Assembly.Location));
        }
        finally
        {
            sourceHost.Dispose();
        }
    }

    public void SetupHostShouldSetResolutionsPaths()
    {
        // Arrange
        DummyClass dummyClass = new();

        string location = typeof(TestSourceHost).Assembly.Location;
        Mock<TestSourceHost> sourceHost = new(location, null!) { CallBase = true };

        try
        {
            // Act
            sourceHost.Object.SetupHost();

            // Assert
            sourceHost.Verify(sh => sh.GetResolutionPaths(location, It.IsAny<bool>()), Times.Once);
        }
        finally
        {
            sourceHost.Object.Dispose();
        }
    }

    public void NoAppDomainShouldGetCreatedWhenDisableAppDomainIsSetToTrue()
    {
        // Arrange
        DummyClass dummyClass = new();
        string runSettingsXml =
        @"<RunSettings>
                <RunConfiguration>
                    <DisableAppDomain>True</DisableAppDomain>
                </RunConfiguration>
            </RunSettings>";

        string location = typeof(TestSourceHost).Assembly.Location;
        Mock<TestSourceHost> testSourceHost = new(location, runSettingsXml) { CallBase = true };

        try
        {
            // Act
            testSourceHost.Object.SetupHost();
            testSourceHost.Object.AppDomain.Should().BeNull();
        }
        finally
        {
            testSourceHost.Object.Dispose();
        }
    }

    public void AppDomainShouldGetCreatedWhenDisableAppDomainIsSetToFalse()
    {
        // Arrange
        DummyClass dummyClass = new();
        string runSettingsXml =
            """
            <RunSettings>
              <RunConfiguration>
                <DisableAppDomain>False</DisableAppDomain>
              </RunConfiguration>
            </RunSettings>
            """;

        string location = typeof(TestSourceHost).Assembly.Location;
        Mock<TestSourceHost> testSourceHost = new(location, runSettingsXml) { CallBase = true };

        try
        {
            // Act
            testSourceHost.Object.SetupHost();
            testSourceHost.Object.AppDomain.Should().NotBeNull();
        }
        finally
        {
            testSourceHost.Object.Dispose();
        }
    }
}

public class DummyClass : MarshalByRefObject
{
    public int AppDomainId => AppDomain.CurrentDomain.Id;

    public string AppDomainAppBase => AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
}
#endif
