// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using Xunit.Abstractions;
using Xunit.Sdk;

// Do not change this namespace without changing the usage in ConditionalTheoryAttribute
namespace Microsoft.AspNetCore.Testing;

internal sealed class ConditionalTheoryDiscoverer : TheoryDiscoverer
{
    public ConditionalTheoryDiscoverer(IMessageSink diagnosticMessageSink)
        : base(diagnosticMessageSink)
    {
    }

    private sealed class OptionsWithPreEnumerationEnabled : ITestFrameworkDiscoveryOptions
    {
        private const string PreEnumerateTheories = "xunit.discovery.PreEnumerateTheories";

        private readonly ITestFrameworkDiscoveryOptions _original;

        public OptionsWithPreEnumerationEnabled(ITestFrameworkDiscoveryOptions original)
            => _original = original;

        public TValue GetValue<TValue>(string name)
            => (name == PreEnumerateTheories) ? (TValue)(object)true : _original.GetValue<TValue>(name);

        public void SetValue<TValue>(string name, TValue value)
            => _original.SetValue(name, value);
    }

    public override IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute)
        => base.Discover(new OptionsWithPreEnumerationEnabled(discoveryOptions), testMethod, theoryAttribute);

    protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute)
    {
        var skipReason = testMethod.EvaluateSkipConditions();
        return skipReason != null
           ? new[] { new SkippedTestCase(skipReason, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) }
           : base.CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute);
    }

    protected override IEnumerable<IXunitTestCase> CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow)
    {
        var skipReason = testMethod.EvaluateSkipConditions();
        if (skipReason == null && dataRow?.Length > 0)
        {
            var obj = dataRow[0];
            if (obj != null)
            {
                var type = obj.GetType();
                var property = type.GetProperty("Skip");
                if (property != null && property.PropertyType.Equals(typeof(string)))
                {
                    skipReason = property.GetValue(obj) as string;
                }
            }
        }

        return skipReason != null ?
            base.CreateTestCasesForSkippedDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow, skipReason)
            : base.CreateTestCasesForDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow);
    }

    protected override IEnumerable<IXunitTestCase> CreateTestCasesForSkippedDataRow(
        ITestFrameworkDiscoveryOptions discoveryOptions,
        ITestMethod testMethod,
        IAttributeInfo theoryAttribute,
        object[] dataRow,
        string skipReason)
    {
        return new[]
        {
                new WORKAROUND_SkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow),
            };
    }

    [Obsolete]
    protected override IXunitTestCase CreateTestCaseForSkippedDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow, string skipReason)
    {
        return new WORKAROUND_SkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow);
    }
}
