// 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.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;

/// <summary>
/// Extension Methods for TestCase Class.
/// </summary>
internal static class TestCaseExtensions
{
    private const string ManagedTypeLabel = "ManagedType";
    private const string ManagedMethodLabel = "ManagedMethod";
    private const string ManagedTypePropertyId = "TestCase." + ManagedTypeLabel;
    private const string ManagedMethodPropertyId = "TestCase." + ManagedMethodLabel;

    internal static readonly TestProperty ManagedTypeProperty = TestProperty.Register(
        id: ManagedTypePropertyId,
        label: ManagedTypeLabel,
        category: string.Empty,
        description: string.Empty,
        valueType: typeof(string),
        validateValueCallback: o => !StringEx.IsNullOrWhiteSpace(o as string),
        attributes: TestPropertyAttributes.Hidden,
        owner: typeof(TestCase));

    internal static readonly TestProperty ManagedMethodProperty = TestProperty.Register(
        id: ManagedMethodPropertyId,
        label: ManagedMethodLabel,
        category: string.Empty,
        description: string.Empty,
        valueType: typeof(string),
        validateValueCallback: o => !StringEx.IsNullOrWhiteSpace(o as string),
        attributes: TestPropertyAttributes.Hidden,
        owner: typeof(TestCase));

    internal static readonly TestProperty HierarchyProperty = TestProperty.Register(
        id: HierarchyConstants.HierarchyPropertyId,
        label: HierarchyConstants.HierarchyLabel,
        category: string.Empty,
        description: string.Empty,
        valueType: typeof(string[]),
        validateValueCallback: null,
        attributes: TestPropertyAttributes.Immutable,
        owner: typeof(TestCase));

    /// <summary>
    /// The test name.
    /// </summary>
    /// <param name="testCase"> The test case. </param>
    /// <param name="testClassName"> The test case's class name. </param>
    /// <returns> The test name, without the class name, if provided. </returns>
    internal static string GetTestName(this TestCase testCase, string? testClassName)
    {
        string fullyQualifiedName = testCase.FullyQualifiedName;

        // Not using Replace because there can be multiple instances of that string.
        string name = FullyQualifiedNameStartsWithTestClassName(fullyQualifiedName, testClassName)
            ? fullyQualifiedName.Substring((testClassName?.Length ?? 0) + 1)
            : fullyQualifiedName;

        return name;
    }

    /// <summary>
    /// The to unit test element.
    /// </summary>
    /// <param name="testCase"> The test case. </param>
    /// <param name="source"> The source. If deployed this is the full path of the source in the deployment directory. </param>
    /// <returns> The converted <see cref="UnitTestElement"/>. </returns>
    internal static UnitTestElement ToUnitTestElementWithUpdatedSource(this TestCase testCase, string source)
    {
        if (testCase.LocalExtensionData is UnitTestElement unitTestElement)
        {
            // If the requested source is different, clone it with updated source.
            // This can happen when there are deployment items in tests.
            // In this case, the source would be the path of the deployment directory.
            // If we don't return UnitTestElement with the correct path to deployment directory, we will
            // end up trying to load the test assembly twice in the same appdomain, once with the default context and once in a LoadFrom context.
            // See https://github.com/microsoft/testfx/issues/6713
            return unitTestElement.TestMethod.AssemblyName == source
                ? unitTestElement
                : unitTestElement.CloneWithUpdatedSource(source);
        }

        string? managedTypeName = testCase.GetManagedType();
        string? legacyTestClassName = testCase.GetPropertyValue(AdapterTestProperties.TestClassNameProperty) as string;
        string? testClassName = testCase.GetClassNameWhenFullyQualifiedNameStartsWith(managedTypeName)
            ?? testCase.GetClassNameWhenFullyQualifiedNameStartsWith(legacyTestClassName)
            ?? managedTypeName
            ?? legacyTestClassName;
        string name = testCase.GetTestName(testClassName);

        var testMethod = new TestMethod(testCase.GetManagedMethod(), testCase.GetHierarchy(), name, testClassName!, source, testCase.DisplayName, testCase.GetPropertyValue<string>(AdapterTestProperties.ParameterTypesProperty, null));

        var dataType = (DynamicDataType)testCase.GetPropertyValue(AdapterTestProperties.TestDynamicDataTypeProperty, (int)DynamicDataType.None);
        if (dataType != DynamicDataType.None)
        {
            string[]? data = testCase.GetPropertyValue<string[]>(AdapterTestProperties.TestDynamicDataProperty, null);

            testMethod.DataType = dataType;
            testMethod.SerializedData = data;
            testMethod.TestCaseIndex = testCase.GetPropertyValue(AdapterTestProperties.TestCaseIndexProperty, 0);
            testMethod.TestDataSourceIgnoreMessage = testCase.GetPropertyValue(AdapterTestProperties.TestDataSourceIgnoreMessageProperty) as string;
        }

        var testElement = new UnitTestElement(testMethod)
        {
            TestCategory = testCase.GetPropertyValue(AdapterTestProperties.TestCategoryProperty) as string[],
            Priority = testCase.GetPropertyValue(AdapterTestProperties.PriorityProperty) as int?,
            UnfoldingStrategy = (TestDataSourceUnfoldingStrategy)testCase.GetPropertyValue(AdapterTestProperties.UnfoldingStrategy, (int)TestDataSourceUnfoldingStrategy.Auto),
        };

        if (testCase.Traits.Any())
        {
            testElement.Traits = [.. testCase.Traits.Select(t => new TestTrait(t.Name, t.Value))];
        }

        string[]? workItemIds = testCase.GetPropertyValue<string[]>(AdapterTestProperties.WorkItemIdsProperty, null);
        if (workItemIds is { Length: > 0 })
        {
            testElement.WorkItemIds = workItemIds;
        }

#if !WINDOWS_UWP && !WIN_UI
        KeyValuePair<string, string>[]? deploymentItems = testCase.GetPropertyValue<KeyValuePair<string, string>[]>(AdapterTestProperties.DeploymentItemsProperty, null);
        if (deploymentItems is { Length: > 0 })
        {
            testElement.DeploymentItems = deploymentItems;
        }
#endif

        testElement.DoNotParallelize = testCase.GetPropertyValue(AdapterTestProperties.DoNotParallelizeProperty, false);

        string[]? encodedResourceLocks = testCase.GetPropertyValue<string[]>(AdapterTestProperties.ResourceLocksProperty, null);
        if (encodedResourceLocks is { Length: > 0 })
        {
            testElement.ResourceLocks = Array.ConvertAll(encodedResourceLocks, ResourceLockInfo.Decode);
        }

        string[]? encodedDependencies = testCase.GetPropertyValue<string[]>(AdapterTestProperties.DependenciesProperty, null);
        if (encodedDependencies is { Length: > 0 })
        {
            testElement.Dependencies = Array.ConvertAll(encodedDependencies, TestDependencyInfo.Decode);
        }

        return testElement;
    }

    internal static string? GetManagedType(this TestCase testCase) => testCase.GetPropertyValue<string>(ManagedTypeProperty, null);

    internal static string? GetManagedMethod(this TestCase testCase) => testCase.GetPropertyValue<string>(ManagedMethodProperty, null);

    private static string? GetClassNameWhenFullyQualifiedNameStartsWith(this TestCase testCase, string? testClassName)
        => testClassName is not null && FullyQualifiedNameStartsWithTestClassName(testCase.FullyQualifiedName, testClassName)
            ? testClassName
            : null;

    private static bool FullyQualifiedNameStartsWithTestClassName(string fullyQualifiedName, string? testClassName)
    {
        int testClassNameLength = testClassName?.Length ?? 0;

        return fullyQualifiedName.Length > testClassNameLength
            && fullyQualifiedName[testClassNameLength] == '.'
            && (testClassName is null || fullyQualifiedName.StartsWith(testClassName, StringComparison.Ordinal));
    }

    internal static string[]? GetHierarchy(this TestCase testCase) => testCase.GetPropertyValue<string[]>(HierarchyProperty, null);

    internal static void SetHierarchy(this TestCase testCase, string?[] value) => testCase.SetPropertyValue(HierarchyProperty, value);
}
