using System.Security.Claims;
using ColaFlow.API.Hubs;
using ColaFlow.Modules.ProjectManagement.Application.Services;
using FluentAssertions;
using Microsoft.AspNetCore.SignalR;
using Moq;
using Xunit;

namespace ColaFlow.IntegrationTests.SignalR;

/// <summary>
/// Tests for SignalR multi-user collaboration scenarios
/// </summary>
public class SignalRCollaborationTests
{
    [Fact]
    public async Task MultipleUsers_JoinSameProject_AllReceiveTypingIndicators()
    {
        // Arrange
        var projectId = Guid.NewGuid();
        var issueId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();

        var user1Id = Guid.NewGuid();
        var user2Id = Guid.NewGuid();
        var user3Id = Guid.NewGuid();

        var mockClients = new Mock<IHubCallerClients>();
        var mockGroups = new Mock<IGroupManager>();
        var mockClientProxy = new Mock<IClientProxy>();
        var mockPermissionService = new Mock<IProjectPermissionService>();

        mockClients.Setup(c => c.OthersInGroup(It.IsAny<string>())).Returns(mockClientProxy.Object);
        mockPermissionService
            .Setup(s => s.IsUserProjectMemberAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);

        // Create hub for user 1
        var hub1 = CreateProjectHub(user1Id, tenantId, "conn-1", mockClients.Object, mockGroups.Object, mockPermissionService.Object);

        // Act - User 1 joins project
        await hub1.JoinProject(projectId);

        // User 1 sends typing indicator
        await hub1.SendTypingIndicator(projectId, issueId, isTyping: true);

        // Assert - Others in group receive typing indicator
        mockClients.Verify(c => c.OthersInGroup($"project-{projectId}"), Times.Once);
        mockClientProxy.Verify(p => p.SendCoreAsync(
            "TypingIndicator",
            It.Is<object[]>(args => args.Length == 1),
            default), Times.Once);
    }

    [Fact]
    public async Task TwoUsers_DifferentProjects_DoNotReceiveEachOthersMessages()
    {
        // Arrange
        var project1 = Guid.NewGuid();
        var project2 = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var user1Id = Guid.NewGuid();
        var user2Id = Guid.NewGuid();

        var mockClients = new Mock<IHubCallerClients>();
        var mockGroups = new Mock<IGroupManager>();
        var mockClientProxy = new Mock<IClientProxy>();
        var mockPermissionService = new Mock<IProjectPermissionService>();

        mockClients.Setup(c => c.OthersInGroup(It.IsAny<string>())).Returns(mockClientProxy.Object);
        mockPermissionService
            .Setup(s => s.IsUserProjectMemberAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);

        var hub1 = CreateProjectHub(user1Id, tenantId, "conn-1", mockClients.Object, mockGroups.Object, mockPermissionService.Object);
        var hub2 = CreateProjectHub(user2Id, tenantId, "conn-2", mockClients.Object, mockGroups.Object, mockPermissionService.Object);

        // Act - Users join different projects
        await hub1.JoinProject(project1);
        await hub2.JoinProject(project2);

        // User 1 sends typing indicator to project 1
        await hub1.SendTypingIndicator(project1, Guid.NewGuid(), isTyping: true);

        // Assert - Message sent to project 1 group only
        mockClients.Verify(c => c.OthersInGroup($"project-{project1}"), Times.Once);
        mockClients.Verify(c => c.OthersInGroup($"project-{project2}"), Times.Never);
    }

    [Fact]
    public async Task User_JoinThenLeaveProject_ReceivesThenStopsReceiving()
    {
        // Arrange
        var projectId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();

        var mockClients = new Mock<IHubCallerClients>();
        var mockGroups = new Mock<IGroupManager>();
        var mockClientProxy = new Mock<IClientProxy>();
        var mockPermissionService = new Mock<IProjectPermissionService>();

        mockClients.Setup(c => c.OthersInGroup(It.IsAny<string>())).Returns(mockClientProxy.Object);
        mockPermissionService
            .Setup(s => s.IsUserProjectMemberAsync(userId, projectId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);

        var hub = CreateProjectHub(userId, tenantId, "conn-1", mockClients.Object, mockGroups.Object, mockPermissionService.Object);

        // Act
        await hub.JoinProject(projectId);
        await hub.LeaveProject(projectId);

        // Assert
        mockGroups.Verify(g => g.AddToGroupAsync("conn-1", $"project-{projectId}", default), Times.Once);
        mockGroups.Verify(g => g.RemoveFromGroupAsync("conn-1", $"project-{projectId}", default), Times.Once);
    }

    [Fact]
    public async Task User_JoinProject_OthersNotifiedOfJoin()
    {
        // Arrange
        var projectId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();

        var mockClients = new Mock<IHubCallerClients>();
        var mockGroups = new Mock<IGroupManager>();
        var mockClientProxy = new Mock<IClientProxy>();
        var mockPermissionService = new Mock<IProjectPermissionService>();

        object? capturedJoinEvent = null;

        mockClients.Setup(c => c.OthersInGroup(It.IsAny<string>())).Returns(mockClientProxy.Object);
        mockClientProxy
            .Setup(p => p.SendCoreAsync("UserJoinedProject", It.IsAny<object[]>(), default))
            .Callback<string, object[], CancellationToken>((method, args, ct) =>
            {
                capturedJoinEvent = args[0];
            })
            .Returns(Task.CompletedTask);

        mockPermissionService
            .Setup(s => s.IsUserProjectMemberAsync(userId, projectId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);

        var hub = CreateProjectHub(userId, tenantId, "conn-1", mockClients.Object, mockGroups.Object, mockPermissionService.Object);

        // Act
        await hub.JoinProject(projectId);

        // Assert
        capturedJoinEvent.Should().NotBeNull();
        var data = capturedJoinEvent as dynamic;
        ((Guid)data!.UserId).Should().Be(userId);
        ((Guid)data.ProjectId).Should().Be(projectId);
    }

    [Fact]
    public async Task User_LeaveProject_OthersNotifiedOfLeave()
    {
        // Arrange
        var projectId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();

        var mockClients = new Mock<IHubCallerClients>();
        var mockGroups = new Mock<IGroupManager>();
        var mockClientProxy = new Mock<IClientProxy>();
        var mockPermissionService = new Mock<IProjectPermissionService>();

        object? capturedLeaveEvent = null;

        mockClients.Setup(c => c.OthersInGroup(It.IsAny<string>())).Returns(mockClientProxy.Object);
        mockClientProxy
            .Setup(p => p.SendCoreAsync("UserLeftProject", It.IsAny<object[]>(), default))
            .Callback<string, object[], CancellationToken>((method, args, ct) =>
            {
                capturedLeaveEvent = args[0];
            })
            .Returns(Task.CompletedTask);

        mockPermissionService
            .Setup(s => s.IsUserProjectMemberAsync(userId, projectId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);

        var hub = CreateProjectHub(userId, tenantId, "conn-1", mockClients.Object, mockGroups.Object, mockPermissionService.Object);

        // Act
        await hub.LeaveProject(projectId);

        // Assert
        capturedLeaveEvent.Should().NotBeNull();
        var data = capturedLeaveEvent as dynamic;
        ((Guid)data!.UserId).Should().Be(userId);
        ((Guid)data.ProjectId).Should().Be(projectId);
    }

    [Fact]
    public async Task MultipleUsers_SameTenant_AllAutoJoinTenantGroup()
    {
        // Arrange
        var tenantId = Guid.NewGuid();
        var user1Id = Guid.NewGuid();
        var user2Id = Guid.NewGuid();
        var user3Id = Guid.NewGuid();

        var mockGroups = new Mock<IGroupManager>();

        var hub1 = CreateTestBaseHub(user1Id, tenantId, "conn-1", mockGroups.Object);
        var hub2 = CreateTestBaseHub(user2Id, tenantId, "conn-2", mockGroups.Object);
        var hub3 = CreateTestBaseHub(user3Id, tenantId, "conn-3", mockGroups.Object);

        // Act
        await hub1.OnConnectedAsync();
        await hub2.OnConnectedAsync();
        await hub3.OnConnectedAsync();

        // Assert
        mockGroups.Verify(g => g.AddToGroupAsync("conn-1", $"tenant-{tenantId}", default), Times.Once);
        mockGroups.Verify(g => g.AddToGroupAsync("conn-2", $"tenant-{tenantId}", default), Times.Once);
        mockGroups.Verify(g => g.AddToGroupAsync("conn-3", $"tenant-{tenantId}", default), Times.Once);
    }

    [Fact]
    public async Task MultipleUsers_DifferentTenants_JoinDifferentTenantGroups()
    {
        // Arrange
        var tenant1 = Guid.NewGuid();
        var tenant2 = Guid.NewGuid();
        var user1Id = Guid.NewGuid();
        var user2Id = Guid.NewGuid();

        var mockGroups = new Mock<IGroupManager>();

        var hub1 = CreateTestBaseHub(user1Id, tenant1, "conn-1", mockGroups.Object);
        var hub2 = CreateTestBaseHub(user2Id, tenant2, "conn-2", mockGroups.Object);

        // Act
        await hub1.OnConnectedAsync();
        await hub2.OnConnectedAsync();

        // Assert
        mockGroups.Verify(g => g.AddToGroupAsync("conn-1", $"tenant-{tenant1}", default), Times.Once);
        mockGroups.Verify(g => g.AddToGroupAsync("conn-2", $"tenant-{tenant2}", default), Times.Once);

        // Verify cross-tenant isolation
        mockGroups.Verify(g => g.AddToGroupAsync("conn-1", $"tenant-{tenant2}", default), Times.Never);
        mockGroups.Verify(g => g.AddToGroupAsync("conn-2", $"tenant-{tenant1}", default), Times.Never);
    }

    [Fact]
    public async Task User_SendsTypingStart_ThenStop_SendsBothEvents()
    {
        // Arrange
        var projectId = Guid.NewGuid();
        var issueId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var userId = Guid.NewGuid();

        var mockClients = new Mock<IHubCallerClients>();
        var mockGroups = new Mock<IGroupManager>();
        var mockClientProxy = new Mock<IClientProxy>();
        var mockPermissionService = new Mock<IProjectPermissionService>();

        var typingStates = new List<bool>();

        mockClients.Setup(c => c.OthersInGroup(It.IsAny<string>())).Returns(mockClientProxy.Object);
        mockClientProxy
            .Setup(p => p.SendCoreAsync("TypingIndicator", It.IsAny<object[]>(), default))
            .Callback<string, object[], CancellationToken>((method, args, ct) =>
            {
                var data = args[0] as dynamic;
                typingStates.Add((bool)data!.IsTyping);
            })
            .Returns(Task.CompletedTask);

        mockPermissionService
            .Setup(s => s.IsUserProjectMemberAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);

        var hub = CreateProjectHub(userId, tenantId, "conn-1", mockClients.Object, mockGroups.Object, mockPermissionService.Object);

        // Act
        await hub.SendTypingIndicator(projectId, issueId, isTyping: true);
        await hub.SendTypingIndicator(projectId, issueId, isTyping: false);

        // Assert
        typingStates.Should().HaveCount(2);
        typingStates[0].Should().BeTrue();
        typingStates[1].Should().BeFalse();
    }

    // Helper methods
    private ProjectHub CreateProjectHub(
        Guid userId,
        Guid tenantId,
        string connectionId,
        IHubCallerClients clients,
        IGroupManager groups,
        IProjectPermissionService permissionService)
    {
        var mockContext = new Mock<HubCallerContext>();
        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);
        mockContext.Setup(c => c.ConnectionAborted).Returns(CancellationToken.None);

        return new ProjectHub(permissionService)
        {
            Context = mockContext.Object,
            Clients = clients,
            Groups = groups
        };
    }

    private TestBaseHub CreateTestBaseHub(
        Guid userId,
        Guid tenantId,
        string connectionId,
        IGroupManager groups)
    {
        var mockContext = new Mock<HubCallerContext>();
        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);

        return new TestBaseHub
        {
            Context = mockContext.Object,
            Groups = groups
        };
    }

    private class TestBaseHub : BaseHub { }
}
