// 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.VisualStudio.TestPlatform.MSTest.TestAdapter;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;

using Moq;

using ITestDataSource = Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ITestDataSource;
using ITestMethod = Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ObjectModel.ITestMethod;

namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations;

internal class TestablePlatformServiceProvider : IPlatformServiceProvider
{
    #region Mock Implementations

    public Mock<IFileOperations> MockFileOperations { get; } = new();

    public Mock<ITraceLogger> MockTraceLogger { get; } = new();

    public Mock<ITestSourceHost> MockTestSourceHost { get; } = new();

#if !WINDOWS_UWP && !WIN_UI
    public Mock<ITestDeployment> MockTestDeployment { get; } = new();
#endif

    public Mock<ISettingsProvider> MockSettingsProvider { get; } = new();

    public Mock<ITestDataSource> MockTestDataSource { get; } = new();

    public Mock<IThreadOperations> MockThreadOperations { get; } = new();

    public Mock<IReflectionOperations> MockReflectionOperations { get; set; } = null!;

    private MockableReflectionOperations? _reflectionOperationsWrapper;

    #endregion

    public IFileOperations FileOperations => MockFileOperations.Object;

    // The getter always returns the mock so tests can verify trace logging. The setter is a no-op:
    // MSTest.TestAdapter's [ModuleInitializer] (MSTestExecutor.SetPlatformLogger) legitimately assigns
    // PlatformServiceProvider.Instance.AdapterTraceLogger when that module is first touched (e.g. via the
    // ToAdapterMessageLogger bridge), so this double must tolerate the assignment exactly as the real
    // PlatformServiceProvider does, rather than throwing.
    public ITraceLogger AdapterTraceLogger { get => MockTraceLogger.Object; set => _ = value; }

#if !WINDOWS_UWP && !WIN_UI
    public ITestDeployment TestDeployment => MockTestDeployment.Object;
#endif

    public ISettingsProvider SettingsProvider => MockSettingsProvider.Object;

    public IThreadOperations ThreadOperations => MockThreadOperations.Object;

    [field: AllowNull]
    [field: MaybeNull]
    public IReflectionOperations ReflectionOperations
    {
        get => MockReflectionOperations != null
            ? (_reflectionOperationsWrapper ?? MockReflectionOperations.Object)
            : field ??= new ReflectionOperations();
        private set;
    }

    public ITestDataSource TestDataSource => MockTestDataSource.Object;

    public TestRunCancellationToken? TestRunCancellationToken { get; set; }

    public bool IsGracefulStopRequested { get; set; }

    public int GetTestContextCallCount { get; private set; }

    public ITestContext GetTestContext(ITestMethod? testMethod, string? testClassFullName, IDictionary<string, object?> properties, IAdapterMessageLogger messageLogger, UnitTestOutcome outcome)
    {
        GetTestContextCallCount++;
        var testContextImpl = new TestContextImplementation(testMethod, testClassFullName, properties, messageLogger, testRunCancellationToken: null);
        testContextImpl.SetOutcome(outcome);
        return testContextImpl;
    }

    public ITestSourceHost CreateTestSourceHost(string source, string? settingsXml) => MockTestSourceHost.Object;

    public void SetupMockReflectionOperations()
    {
        MockReflectionOperations = new Mock<IReflectionOperations>();
        _reflectionOperationsWrapper = MockableReflectionOperations.Create(MockReflectionOperations);
    }

    public void SetupMockReflectionOperations(Mock<IReflectionOperations> mock)
    {
        MockReflectionOperations = mock;
        _reflectionOperationsWrapper = MockableReflectionOperations.Create(mock);
    }
}
