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

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.EventLog;

namespace Microsoft.AspNetCore.Tests
{
    public class WebApplicationFunctionalTests : LoggedTest
    {
        [Fact]
        public async Task LoggingConfigurationSectionPassedToLoggerByDefault()
        {
            var contentRootPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
            Directory.CreateDirectory(contentRootPath);

            try
            {
                await File.WriteAllTextAsync(Path.Combine(contentRootPath, "appsettings.json"), @"
{
    ""Logging"": {
        ""LogLevel"": {
            ""Default"": ""Warning""
        }
    }
}");

                await using var app = WebApplication.Create(new[] { "--contentRoot", contentRootPath });

                var factory = (ILoggerFactory)app.Services.GetService(typeof(ILoggerFactory));
                var logger = factory.CreateLogger("Test");

                logger.Log(LogLevel.Information, 0, "Message", null, (s, e) =>
                {
                    Assert.True(false);
                    return string.Empty;
                });

                var logWritten = false;
                logger.Log(LogLevel.Warning, 0, "Message", null, (s, e) =>
                {
                    logWritten = true;
                    return string.Empty;
                });

                Assert.True(logWritten);
            }
            finally
            {
                Directory.Delete(contentRootPath, recursive: true);
            }
        }

        [Fact]
        public async Task EnvironmentSpecificLoggingConfigurationSectionPassedToLoggerByDefault()
        {
            var contentRootPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
            Directory.CreateDirectory(contentRootPath);

            try
            {
                await File.WriteAllTextAsync(Path.Combine(contentRootPath, "appsettings.Development.json"), @"
{
    ""Logging"": {
        ""LogLevel"": {
            ""Default"": ""Warning""
        }
    }
}");

                var app = WebApplication.Create(new[] { "--environment", "Development", "--contentRoot", contentRootPath });

                var factory = (ILoggerFactory)app.Services.GetService(typeof(ILoggerFactory));
                var logger = factory.CreateLogger("Test");

                logger.Log(LogLevel.Information, 0, "Message", null, (s, e) =>
                {
                    Assert.True(false);
                    return string.Empty;
                });

                var logWritten = false;
                logger.Log(LogLevel.Warning, 0, "Message", null, (s, e) =>
                {
                    logWritten = true;
                    return string.Empty;
                });

                Assert.True(logWritten);
            }
            finally
            {
                Directory.Delete(contentRootPath, recursive: true);
            }
        }

        [Fact]
        public async Task LoggingConfigurationReactsToRuntimeChanges()
        {
            var contentRootPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
            Directory.CreateDirectory(contentRootPath);

            try
            {
                await File.WriteAllTextAsync(Path.Combine(contentRootPath, "appsettings.json"), @"
{
    ""Logging"": {
        ""LogLevel"": {
            ""Default"": ""Error""
        }
    }
}");

                var builder = WebApplication.CreateBuilder(new WebApplicationOptions
                {
                    ContentRootPath = contentRootPath,
                });

                // Disable the EventLogLoggerProvider because HostBuilder.ConfigureDefaults() configures it to log everything warning and higher which overrides non-provider-specific config.
                // https://github.com/dotnet/runtime/blob/8048fe613933a1cd91e3fad6d571c74f726143ef/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L238
                builder.Logging.AddFilter<EventLogLoggerProvider>(_ => false);

                await using var app = builder.Build();

                var factory = (ILoggerFactory)app.Services.GetService(typeof(ILoggerFactory));
                var logger = factory.CreateLogger("Test");

                Assert.False(logger.IsEnabled(LogLevel.Warning));

                logger.Log(LogLevel.Warning, 0, "Message", null, (s, e) =>
                {
                    Assert.True(false);
                    return string.Empty;
                });

                // Lower log level from Error to Warning and wait for logging to react to the config changes.
                var configChangedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
                using var registration = app.Configuration.GetReloadToken().RegisterChangeCallback(
                    tcs => ((TaskCompletionSource)tcs).SetResult(), configChangedTcs);

                await File.WriteAllTextAsync(Path.Combine(contentRootPath, "appsettings.json"), @"
{
    ""Logging"": {
        ""LogLevel"": {
            ""Default"": ""Warning""
        }
    }
}");

                // Wait for a config change notification because logging will not react until this is fired. Even then, it won't react immediately
                // so we loop until success or a timeout.
                await configChangedTcs.Task.DefaultTimeout();

                var timeoutTicks = Environment.TickCount64 + Testing.TaskExtensions.DefaultTimeoutDuration;
                var logWritten = false;

                while (!logWritten && Environment.TickCount < timeoutTicks)
                {
                    logger.Log(LogLevel.Warning, 0, "Message", null, (s, e) =>
                    {
                        logWritten = true;
                        return string.Empty;
                    });
                }

                Assert.True(logWritten);
                Assert.True(logger.IsEnabled(LogLevel.Warning));
            }
            finally
            {
                Directory.Delete(contentRootPath, recursive: true);
            }
        }
    }
}
