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

using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Extensions.Logging.Testing.Tests;

[LogLevel(LogLevel.Debug)]
[ShortClassName]
public class LoggedTestXunitTests : TestLoggedTest
{
    private readonly ITestOutputHelper _output;

    public LoggedTestXunitTests(ITestOutputHelper output)
    {
        _output = output;
    }

    [Fact]
    public void LoggedFactInitializesLoggedTestProperties()
    {
        Assert.NotNull(Logger);
        Assert.NotNull(LoggerFactory);
        Assert.NotNull(TestSink);
        Assert.NotNull(TestOutputHelper);
    }

    [Theory]
    [InlineData("Hello world")]
    public void LoggedTheoryInitializesLoggedTestProperties(string argument)
    {
        Assert.NotNull(Logger);
        Assert.NotNull(LoggerFactory);
        Assert.NotNull(TestSink);
        Assert.NotNull(TestOutputHelper);
        // Use the test argument
        Assert.NotNull(argument);
    }

    [ConditionalFact]
    public void ConditionalLoggedFactGetsInitializedLoggerFactory()
    {
        Assert.NotNull(Logger);
        Assert.NotNull(LoggerFactory);
        Assert.NotNull(TestSink);
        Assert.NotNull(TestOutputHelper);
    }

    [ConditionalTheory]
    [InlineData("Hello world")]
    public void LoggedConditionalTheoryInitializesLoggedTestProperties(string argument)
    {
        Assert.NotNull(Logger);
        Assert.NotNull(LoggerFactory);
        Assert.NotNull(TestSink);
        Assert.NotNull(TestOutputHelper);
        // Use the test argument
        Assert.NotNull(argument);
    }

    [Fact]
    [LogLevel(LogLevel.Information)]
    public void LoggedFactFilteredByMethodLogLevel()
    {
        Logger.LogInformation("Information");
        Logger.LogDebug("Debug");

        var message = Assert.Single(TestSink.Writes);
        Assert.Equal(LogLevel.Information, message.LogLevel);
        Assert.Equal("Information", message.Formatter(message.State, null));
    }

    [Fact]
    public void LoggedFactFilteredByClassLogLevel()
    {
        Logger.LogDebug("Debug");
        Logger.LogTrace("Trace");

        var message = Assert.Single(TestSink.Writes);
        Assert.Equal(LogLevel.Debug, message.LogLevel);
        Assert.Equal("Debug", message.Formatter(message.State, null));
    }

    [Theory]
    [InlineData("Hello world")]
    [LogLevel(LogLevel.Information)]
    public void LoggedTheoryFilteredByLogLevel(string argument)
    {
        Logger.LogInformation("Information");
        Logger.LogDebug("Debug");

        var message = Assert.Single(TestSink.Writes);
        Assert.Equal(LogLevel.Information, message.LogLevel);
        Assert.Equal("Information", message.Formatter(message.State, null));

        // Use the test argument
        Assert.NotNull(argument);
    }

    [Fact]
    public void AddTestLoggingUpdatedWhenLoggerFactoryIsSet()
    {
        var loggerFactory = new LoggerFactory();
        var serviceCollection = new ServiceCollection();

        LoggerFactory = loggerFactory;
        AddTestLogging(serviceCollection);

        Assert.Same(loggerFactory, serviceCollection.BuildServiceProvider().GetRequiredService<ILoggerFactory>());
    }

    [ConditionalTheory]
    [EnvironmentVariableSkipCondition("ASPNETCORE_TEST_LOG_DIR", "")] // The test name is only generated when logging is enabled via the environment variable
    [InlineData(null)]
    public void LoggedTheoryNullArgumentsAreEscaped(string argument)
    {
        Assert.NotNull(LoggerFactory);
        Assert.Equal($"{nameof(LoggedTheoryNullArgumentsAreEscaped)}_null", ResolvedTestMethodName);
        // Use the test argument
        Assert.Null(argument);
    }

    [Fact]
    public void AdditionalSetupInvoked()
    {
        Assert.True(SetupInvoked);
    }

    [Fact]
    public void MessageWrittenEventInvoked()
    {
        WriteContext context = null;
        TestSink.MessageLogged += ctx => context = ctx;
        Logger.LogInformation("Information");
        Assert.Equal(TestSink.Writes.Single(), context);
    }

    [Fact]
    public void ScopeStartedEventInvoked()
    {
        BeginScopeContext context = null;
        TestSink.ScopeStarted += ctx => context = ctx;
        using (Logger.BeginScope("Scope")) { }
        Assert.Equal(TestSink.Scopes.Single(), context);
    }
}

public class LoggedTestXunitLogLevelTests : LoggedTest
{
    [Fact]
    public void LoggedFactFilteredByAssemblyLogLevel()
    {
        Logger.LogTrace("Trace");

        var message = Assert.Single(TestSink.Writes);
        Assert.Equal(LogLevel.Trace, message.LogLevel);
        Assert.Equal("Trace", message.Formatter(message.State, null));
    }
}

public class LoggedTestXunitInitializationTests : TestLoggedTest
{
    [Fact]
    public void ITestOutputHelperInitializedByDefault()
    {
        Assert.True(ITestOutputHelperIsInitialized);
    }
}

public class TestLoggedTest : LoggedTest
{
    public bool SetupInvoked { get; private set; } = false;
    public bool ITestOutputHelperIsInitialized { get; private set; } = false;

    public override void Initialize(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper)
    {
        base.Initialize(context, methodInfo, testMethodArguments, testOutputHelper);

        try
        {
            TestOutputHelper.WriteLine("Test");
            ITestOutputHelperIsInitialized = true;
        }
        catch { }
        SetupInvoked = true;
    }
}
