using System.Security.Claims;
using ColaFlow.API.Hubs;
using ColaFlow.API.Tests.Helpers;
using FluentAssertions;
using Microsoft.AspNetCore.SignalR;
using Moq;

namespace ColaFlow.API.Tests.Hubs;

public class NotificationHubTests
{
    private readonly Mock<IHubCallerClients> _mockClients;
    private readonly Mock<HubCallerContext> _mockContext;
    private readonly Mock<ISingleClientProxy> _mockCallerProxy;
    private readonly NotificationHub _hub;
    private readonly Guid _userId;
    private readonly Guid _tenantId;

    public NotificationHubTests()
    {
        _mockClients = new Mock<IHubCallerClients>();
        _mockContext = new Mock<HubCallerContext>();
        _mockCallerProxy = new Mock<ISingleClientProxy>();

        _userId = Guid.NewGuid();
        _tenantId = Guid.NewGuid();

        // Setup context with valid claims
        var claims = new[]
        {
            new Claim("sub", _userId.ToString()),
            new Claim("tenant_id", _tenantId.ToString())
        };

        _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims)));
        _mockContext.Setup(c => c.ConnectionId).Returns("test-connection-id");

        // Setup clients mock
        _mockClients.Setup(c => c.Caller).Returns(_mockCallerProxy.Object);

        _hub = new NotificationHub
        {
            Clients = _mockClients.Object,
            Context = _mockContext.Object
        };
    }

    [Fact]
    public async Task MarkAsRead_WithValidNotificationId_SendsNotificationReadToCaller()
    {
        // Arrange
        var notificationId = Guid.NewGuid();

        // Act
        await _hub.MarkAsRead(notificationId);

        // Assert
        _mockClients.Verify(c => c.Caller, Times.Once);
        _mockCallerProxy.Verify(p => p.SendCoreAsync(
            "NotificationRead",
            It.Is<object[]>(args => args.Length == 1),
            default), Times.Once);
    }

    [Fact]
    public async Task MarkAsRead_ContainsCorrectNotificationId()
    {
        // Arrange
        var notificationId = Guid.NewGuid();
        object? capturedData = null;

        _mockCallerProxy
            .Setup(p => p.SendCoreAsync("NotificationRead", It.IsAny<object[]>(), default))
            .Callback<string, object[], CancellationToken>((method, args, ct) =>
            {
                capturedData = args[0];
            })
            .Returns(Task.CompletedTask);

        // Act
        await _hub.MarkAsRead(notificationId);

        // Assert
        capturedData.Should().NotBeNull();
        TestHelpers.GetPropertyValue<Guid>(capturedData!, "NotificationId").Should().Be(notificationId);
    }

    [Fact]
    public async Task MarkAsRead_ContainsReadAtTimestamp()
    {
        // Arrange
        var notificationId = Guid.NewGuid();
        object? capturedData = null;

        _mockCallerProxy
            .Setup(p => p.SendCoreAsync("NotificationRead", It.IsAny<object[]>(), default))
            .Callback<string, object[], CancellationToken>((method, args, ct) =>
            {
                capturedData = args[0];
            })
            .Returns(Task.CompletedTask);

        // Act
        await _hub.MarkAsRead(notificationId);

        // Assert
        capturedData.Should().NotBeNull();
        TestHelpers.GetPropertyValue<DateTime>(capturedData!, "ReadAt").Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public async Task MarkAsRead_MultipleNotifications_SendsMultipleEvents()
    {
        // Arrange
        var notificationId1 = Guid.NewGuid();
        var notificationId2 = Guid.NewGuid();
        var notificationId3 = Guid.NewGuid();

        // Act
        await _hub.MarkAsRead(notificationId1);
        await _hub.MarkAsRead(notificationId2);
        await _hub.MarkAsRead(notificationId3);

        // Assert
        _mockCallerProxy.Verify(p => p.SendCoreAsync(
            "NotificationRead",
            It.IsAny<object[]>(),
            default), Times.Exactly(3));
    }

    [Fact]
    public async Task MarkAsRead_OnlySendsToCaller_NotOtherClients()
    {
        // Arrange
        var notificationId = Guid.NewGuid();
        var mockOthersProxy = new Mock<IClientProxy>();
        _mockClients.Setup(c => c.Others).Returns(mockOthersProxy.Object);

        // Act
        await _hub.MarkAsRead(notificationId);

        // Assert
        _mockCallerProxy.Verify(p => p.SendCoreAsync(
            "NotificationRead",
            It.IsAny<object[]>(),
            default), Times.Once);
        mockOthersProxy.Verify(p => p.SendCoreAsync(
            It.IsAny<string>(),
            It.IsAny<object[]>(),
            default), Times.Never);
    }

    [Fact]
    public void MarkAsRead_WithMissingUserId_ThrowsUnauthorizedException()
    {
        // Arrange
        var notificationId = Guid.NewGuid();
        var claims = new[] { new Claim("tenant_id", _tenantId.ToString()) };
        _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims)));

        var hubWithoutUserId = new NotificationHub
        {
            Clients = _mockClients.Object,
            Context = _mockContext.Object
        };

        // Act & Assert
        var act = async () => await hubWithoutUserId.MarkAsRead(notificationId);
        act.Should().ThrowAsync<UnauthorizedAccessException>()
            .WithMessage("User ID not found in token");
    }

    [Fact]
    public async Task MarkAsRead_EmptyGuid_StillProcesses()
    {
        // Arrange
        var emptyGuid = Guid.Empty;

        // Act
        var act = async () => await _hub.MarkAsRead(emptyGuid);

        // Assert
        await act.Should().NotThrowAsync();
        _mockCallerProxy.Verify(p => p.SendCoreAsync(
            "NotificationRead",
            It.Is<object[]>(args => args.Length == 1),
            default), Times.Once);
    }
}
