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

using BasicTestApp;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
using OpenQA.Selenium;
using Xunit.Abstractions;

namespace Microsoft.AspNetCore.Components.E2ETest.Tests;

public class WebAssemblyLoggingTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
{
    public WebAssemblyLoggingTest(
        BrowserFixture browserFixture,
        ToggleExecutionModeServerFixture<Program> serverFixture,
        ITestOutputHelper output)
        : base(browserFixture, serverFixture, output)
    {
    }

    protected override void InitializeAsyncCore()
    {
        Navigate(ServerPathBase, noReload: false);
        Browser.MountTestComponent<ErrorComponent>();
        Browser.Exists(By.Id("blazor-error-ui"));

        var errorUi = Browser.Exists(By.Id("blazor-error-ui"));
        Assert.Equal("none", errorUi.GetCssValue("display"));
    }

    [Fact]
    public void LogsSimpleExceptionsUsingLogger()
    {
        Browser.Exists(By.Id("throw-simple-exception")).Click();
        Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: block;']"), TimeSpan.FromSeconds(10));
        AssertLogContainsCriticalMessages(
            "crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]",
            "[Custom logger] Unhandled exception rendering component: Doing something that won't work!",
            "System.InvalidTimeZoneException: Doing something that won't work!",
            "at BasicTestApp.ErrorComponent.ThrowSimple");
    }

    [Fact]
    public void LogsInnerExceptionsUsingLogger()
    {
        Browser.Exists(By.Id("throw-inner-exception")).Click();
        Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: block;']"), TimeSpan.FromSeconds(10));
        AssertLogContainsCriticalMessages(
            "crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]",
            "[Custom logger] Unhandled exception rendering component: Here is the outer exception",
            "System.InvalidTimeZoneException: Here is the outer exception",
            "System.ArithmeticException: Here is the inner exception",
            "at BasicTestApp.ErrorComponent.ThrowInner");
    }

    [Fact]
    public void LogsAggregateExceptionsUsingLogger()
    {
        Browser.Exists(By.Id("throw-aggregate-exception")).Click();
        Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: block;']"), TimeSpan.FromSeconds(10));
        AssertLogContainsCriticalMessages(
            "crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]",
            "[Custom logger] Unhandled exception rendering component: Aggregate exception 1",
            "System.InvalidTimeZoneException: Aggregate exception 1",
            "[Custom logger] Unhandled exception rendering component: Aggregate exception 2",
            "System.InvalidTimeZoneException: Aggregate exception 2",
            "[Custom logger] Unhandled exception rendering component: Aggregate exception 3",
            "System.InvalidTimeZoneException: Aggregate exception 3");
    }

    [Fact]
    public void LogsUsingCustomLogger()
    {
        Browser.MountTestComponent<LoggingComponent>();
        Browser.Exists(By.Id("blazor-error-ui"));
        Browser.Exists(By.Id("log-trace"));

        ((IJavaScriptExecutor)Browser).ExecuteScript("console.info('Test log message')");

        // None of these severity levels are displayed by default, so at the end
        // we'll continue to see "Test log message" as the most recent output
        Browser.Exists(By.Id("log-none")).Click();
        Browser.Exists(By.Id("log-trace")).Click();
        Browser.Exists(By.Id("log-debug")).Click();
        Browser.Exists(By.Id("log-information")).Click();
        // The Warning minimum log-level is only set on the PrependMessage
        // logger so the last info log will be processed by the default
        // logger but not the PrependMessage one.
        AssertLastLogMessage(LogLevel.Info, "info: BasicTestApp.ErrorComponent[0]");

        // These severity levels are displayed
        Browser.Exists(By.Id("log-warning")).Click();
        AssertLastLogMessage(LogLevel.Warning, "[Custom logger] This is a Warning message with count=5");
        Browser.Exists(By.Id("log-error")).Click();
        AssertLastLogMessage(LogLevel.Severe, "[Custom logger] This is a Error message with count=6");

        // All the preceding levels don't cause the error UI to appear
        var errorUi = Browser.Exists(By.Id("blazor-error-ui"));
        Assert.Equal("none", errorUi.GetCssValue("display"));

        // ... but "Critical" level does
        Browser.Exists(By.Id("log-critical")).Click();
        AssertLastLogMessage(LogLevel.Severe, "[Custom logger] This is a Critical message with count=7");
        Assert.Equal("block", errorUi.GetCssValue("display"));
    }

    void AssertLastLogMessage(LogLevel level, string message)
    {
        var log = Browser.Manage().Logs.GetLog(LogType.Browser);
        var lastEntry = log[log.Count - 1];
        Assert.Equal(level, lastEntry.Level);

        // Selenium prefixes the message with various bits of internal info, so use "Contains"
        Assert.Contains(message, lastEntry.Message);
    }

    void AssertLogContainsCriticalMessages(params string[] messages)
    {
        var log = Browser.Manage().Logs.GetLog(LogType.Browser);
        foreach (var message in messages)
        {
            Assert.Contains(log, entry =>
            {
                return entry.Level == LogLevel.Severe
                && entry.Message.Contains(message);
            });
        }
    }
}
