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

namespace ColaFlow.API.Tests.Hubs;

public class BaseHubTests
{
    private readonly Mock<IHubCallerClients> _mockClients;
    private readonly Mock<IGroupManager> _mockGroups;
    private readonly Mock<HubCallerContext> _mockContext;
    private readonly TestHub _hub;

    public BaseHubTests()
    {
        _mockClients = new Mock<IHubCallerClients>();
        _mockGroups = new Mock<IGroupManager>();
        _mockContext = new Mock<HubCallerContext>();

        _hub = new TestHub
        {
            Clients = _mockClients.Object,
            Groups = _mockGroups.Object,
            Context = _mockContext.Object
        };
    }

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

        // Act
        var result = _hub.GetCurrentUserIdPublic();

        // Assert
        result.Should().Be(userId);
    }

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

        // Act
        var result = _hub.GetCurrentUserIdPublic();

        // Assert
        result.Should().Be(userId);
    }

    [Fact]
    public void GetCurrentUserId_WithMissingClaim_ThrowsUnauthorizedException()
    {
        // Arrange
        var claims = new[] { new Claim("some_other_claim", "value") };
        _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims)));

        // Act & Assert
        var act = () => _hub.GetCurrentUserIdPublic();
        act.Should().Throw<UnauthorizedAccessException>()
            .WithMessage("User ID not found in token");
    }

    [Fact]
    public void GetCurrentUserId_WithInvalidGuid_ThrowsUnauthorizedException()
    {
        // Arrange
        var claims = new[] { new Claim("sub", "not-a-guid") };
        _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims)));

        // Act & Assert
        var act = () => _hub.GetCurrentUserIdPublic();
        act.Should().Throw<UnauthorizedAccessException>()
            .WithMessage("User ID not found in token");
    }

    [Fact]
    public void GetCurrentUserId_WithNullUser_ThrowsUnauthorizedException()
    {
        // Arrange
        _mockContext.Setup(c => c.User).Returns((ClaimsPrincipal)null!);

        // Act & Assert
        var act = () => _hub.GetCurrentUserIdPublic();
        act.Should().Throw<UnauthorizedAccessException>()
            .WithMessage("User ID not found in token");
    }

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

        // Act
        var result = _hub.GetCurrentTenantIdPublic();

        // Assert
        result.Should().Be(tenantId);
    }

    [Fact]
    public void GetCurrentTenantId_WithMissingClaim_ThrowsUnauthorizedException()
    {
        // Arrange
        var claims = new[] { new Claim("some_other_claim", "value") };
        _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims)));

        // Act & Assert
        var act = () => _hub.GetCurrentTenantIdPublic();
        act.Should().Throw<UnauthorizedAccessException>()
            .WithMessage("Tenant ID not found in token");
    }

    [Fact]
    public void GetCurrentTenantId_WithInvalidGuid_ThrowsUnauthorizedException()
    {
        // Arrange
        var claims = new[] { new Claim("tenant_id", "not-a-guid") };
        _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims)));

        // Act & Assert
        var act = () => _hub.GetCurrentTenantIdPublic();
        act.Should().Throw<UnauthorizedAccessException>()
            .WithMessage("Tenant ID not found in token");
    }

    [Fact]
    public void GetTenantGroupName_ReturnsCorrectFormat()
    {
        // Arrange
        var tenantId = Guid.NewGuid();

        // Act
        var result = _hub.GetTenantGroupNamePublic(tenantId);

        // Assert
        result.Should().Be($"tenant-{tenantId}");
    }

    [Fact]
    public async Task OnConnectedAsync_WithValidClaims_AutoJoinsTenantGroup()
    {
        // Arrange
        var userId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var connectionId = "test-connection-id";

        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(connectionId);

        // Act
        await _hub.OnConnectedAsync();

        // Assert
        _mockGroups.Verify(g => g.AddToGroupAsync(
            connectionId,
            $"tenant-{tenantId}",
            default), Times.Once);
    }

    [Fact]
    public async Task OnConnectedAsync_WithMissingTenantId_AbortsConnection()
    {
        // Arrange
        var userId = Guid.NewGuid();
        var claims = new[] { new Claim("sub", userId.ToString()) };

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

        // Act
        await _hub.OnConnectedAsync();

        // Assert
        _mockContext.Verify(c => c.Abort(), Times.Once);
        _mockGroups.Verify(g => g.AddToGroupAsync(
            It.IsAny<string>(),
            It.IsAny<string>(),
            default), Times.Never);
    }

    [Fact]
    public async Task OnConnectedAsync_WithMissingUserId_AbortsConnection()
    {
        // Arrange
        var tenantId = Guid.NewGuid();
        var claims = new[] { 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");

        // Act
        await _hub.OnConnectedAsync();

        // Assert
        _mockContext.Verify(c => c.Abort(), Times.Once);
        _mockGroups.Verify(g => g.AddToGroupAsync(
            It.IsAny<string>(),
            It.IsAny<string>(),
            default), Times.Never);
    }

    [Fact]
    public async Task OnDisconnectedAsync_WithValidClaims_CompletesSuccessfully()
    {
        // Arrange
        var userId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        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)));

        // Act
        var act = async () => await _hub.OnDisconnectedAsync(null);

        // Assert
        await act.Should().NotThrowAsync();
    }

    // Test Hub implementation to expose protected methods
    private class TestHub : BaseHub
    {
        public Guid GetCurrentUserIdPublic() => GetCurrentUserId();
        public Guid GetCurrentTenantIdPublic() => GetCurrentTenantId();
        public string GetTenantGroupNamePublic(Guid tenantId) => GetTenantGroupName(tenantId);
    }
}
