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

using System.Text;

namespace Microsoft.AspNetCore.Components;

public class EventCallbackTest
{
    [Fact]
    public async Task EventCallback_Default()
    {
        // Arrange
        var callback = default(EventCallback);

        // Act & Assert (Does not throw)
        await callback.InvokeAsync();
    }

    [Fact]
    public async Task EventCallbackOfT_Default()
    {
        // Arrange
        var callback = default(EventCallback<EventArgs>);

        // Act & Assert (Does not throw)
        await callback.InvokeAsync();
    }

    [Fact]
    public async Task EventCallback_NullReceiver()
    {
        // Arrange
        int runCount = 0;
        var callback = new EventCallback(null, (Action)(() => runCount++));

        // Act
        await callback.InvokeAsync();

        // Assert
        Assert.Equal(1, runCount);
    }

    [Fact]
    public async Task EventCallbackOfT_NullReceiver()
    {
        // Arrange
        int runCount = 0;
        var callback = new EventCallback<EventArgs>(null, (Action)(() => runCount++));

        // Act
        await callback.InvokeAsync();

        // Assert
        Assert.Equal(1, runCount);
    }

    [Fact]
    public async Task EventCallback_Action_Null()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        var callback = new EventCallback(component, (Action)(() => runCount++));

        // Act
        await callback.InvokeAsync();

        // Assert
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallback_Action_IgnoresArg()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        var callback = new EventCallback(component, (Action)(() => runCount++));

        // Act
        await callback.InvokeAsync(new EventArgs());

        // Assert
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallback_ActionT_Null()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        EventArgs arg = null;
        var callback = new EventCallback(component, (Action<EventArgs>)((e) => { arg = e; runCount++; }));

        // Act
        await callback.InvokeAsync();

        // Assert
        Assert.Null(arg);
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallback_ActionT_Arg()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        EventArgs arg = null;
        var callback = new EventCallback(component, (Action<EventArgs>)((e) => { arg = e; runCount++; }));

        // Act
        await callback.InvokeAsync(new EventArgs());

        // Assert
        Assert.NotNull(arg);
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallback_ActionT_Arg_ValueType()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        int arg = -1;
        var callback = new EventCallback(component, (Action<int>)((e) => { arg = e; runCount++; }));

        // Act
        await callback.InvokeAsync(17);

        // Assert
        Assert.Equal(17, arg);
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallback_ActionT_ArgMismatch()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        EventArgs arg = null;
        var callback = new EventCallback(component, (Action<EventArgs>)((e) => { arg = e; runCount++; }));

        // Act & Assert
        await Assert.ThrowsAsync<ArgumentException>(() =>
        {
            return callback.InvokeAsync(new StringBuilder());
        });
    }

    [Fact]
    public async Task EventCallback_FuncTask_Null()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        var callback = new EventCallback(component, (Func<Task>)(() => { runCount++; return Task.CompletedTask; }));

        // Act
        await callback.InvokeAsync();

        // Assert
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallback_FuncTask_IgnoresArg()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        var callback = new EventCallback(component, (Func<Task>)(() => { runCount++; return Task.CompletedTask; }));

        // Act
        await callback.InvokeAsync(new EventArgs());

        // Assert
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallback_FuncTTask_Null()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        EventArgs arg = null;
        var callback = new EventCallback(component, (Func<EventArgs, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));

        // Act
        await callback.InvokeAsync();

        // Assert
        Assert.Null(arg);
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallback_FuncTTask_Arg()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        EventArgs arg = null;
        var callback = new EventCallback(component, (Func<EventArgs, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));

        // Act
        await callback.InvokeAsync(new EventArgs());

        // Assert
        Assert.NotNull(arg);
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallback_FuncTTask_Arg_ValueType()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        int arg = -1;
        var callback = new EventCallback(component, (Func<int, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));

        // Act
        await callback.InvokeAsync(17);

        // Assert
        Assert.Equal(17, arg);
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallback_FuncTTask_ArgMismatch()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        EventArgs arg = null;
        var callback = new EventCallback(component, (Func<EventArgs, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));

        // Act & Assert
        await Assert.ThrowsAsync<ArgumentException>(() =>
        {
            return callback.InvokeAsync(new StringBuilder());
        });
    }

    [Fact]
    public async Task EventCallbackOfT_Action_Null()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        var callback = new EventCallback<EventArgs>(component, (Action)(() => runCount++));

        // Act
        await callback.InvokeAsync();

        // Assert
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallbackOfT_Action_IgnoresArg()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        var callback = new EventCallback<EventArgs>(component, (Action)(() => runCount++));

        // Act
        await callback.InvokeAsync(new EventArgs());

        // Assert
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallbackOfT_ActionT_Null()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        EventArgs arg = null;
        var callback = new EventCallback<EventArgs>(component, (Action<EventArgs>)((e) => { arg = e; runCount++; }));

        // Act
        await callback.InvokeAsync();

        // Assert
        Assert.Null(arg);
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallbackOfT_ActionT_Arg()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        EventArgs arg = null;
        var callback = new EventCallback<EventArgs>(component, (Action<EventArgs>)((e) => { arg = e; runCount++; }));

        // Act
        await callback.InvokeAsync(new EventArgs());

        // Assert
        Assert.NotNull(arg);
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallbackOfT_FuncTask_Null()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        var callback = new EventCallback<EventArgs>(component, (Func<Task>)(() => { runCount++; return Task.CompletedTask; }));

        // Act
        await callback.InvokeAsync();

        // Assert
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallbackOfT_FuncTask_IgnoresArg()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        var callback = new EventCallback<EventArgs>(component, (Func<Task>)(() => { runCount++; return Task.CompletedTask; }));

        // Act
        await callback.InvokeAsync(new EventArgs());

        // Assert
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallbackOfT_FuncTTask_Null()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        EventArgs arg = null;
        var callback = new EventCallback<EventArgs>(component, (Func<EventArgs, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));

        // Act
        await callback.InvokeAsync();

        // Assert
        Assert.Null(arg);
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    [Fact]
    public async Task EventCallbackOfT_FuncTTask_Arg()
    {
        // Arrange
        var component = new EventCountingComponent();

        int runCount = 0;
        EventArgs arg = null;
        var callback = new EventCallback<EventArgs>(component, (Func<EventArgs, Task>)((e) => { arg = e; runCount++; return Task.CompletedTask; }));

        // Act
        await callback.InvokeAsync(new EventArgs());

        // Assert
        Assert.NotNull(arg);
        Assert.Equal(1, runCount);
        Assert.Equal(1, component.Count);
    }

    private class EventCountingComponent : IComponent, IHandleEvent
    {
        public int Count;

        public Task HandleEventAsync(EventCallbackWorkItem item, object arg)
        {
            Count++;
            return item.InvokeAsync(arg);
        }

        public void Attach(RenderHandle renderHandle) => throw new NotImplementedException();

        public Task SetParametersAsync(ParameterView parameters) => throw new NotImplementedException();
    }
}
