using ColaFlow.Modules.Mcp.Application.DTOs.Notifications;
using ColaFlow.Modules.Mcp.Infrastructure.Hubs;
using ColaFlow.Modules.Mcp.Infrastructure.Services;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace ColaFlow.Modules.Mcp.Tests.Services;

public class McpNotificationServiceTests
{
    private readonly Mock<IHubContext<McpNotificationHub>> _mockHubContext;
    private readonly Mock<ILogger<McpNotificationService>> _mockLogger;
    private readonly Mock<IHubClients> _mockClients;
    private readonly Mock<IClientProxy> _mockClientProxy;
    private readonly McpNotificationService _service;

    public McpNotificationServiceTests()
    {
        _mockHubContext = new Mock<IHubContext<McpNotificationHub>>();
        _mockLogger = new Mock<ILogger<McpNotificationService>>();
        _mockClients = new Mock<IHubClients>();
        _mockClientProxy = new Mock<IClientProxy>();

        _mockHubContext.Setup(h => h.Clients).Returns(_mockClients.Object);

        _service = new McpNotificationService(_mockHubContext.Object, _mockLogger.Object);
    }

    [Fact]
    public async Task NotifyPendingChangeCreatedAsync_SendsNotificationToCorrectGroups()
    {
        // Arrange
        var pendingChangeId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var notification = new PendingChangeCreatedNotification
        {
            NotificationType = "PendingChangeCreated",
            PendingChangeId = pendingChangeId,
            ToolName = "create_epic",
            EntityType = "Epic",
            Operation = "CREATE",
            Summary = "Create Epic: Test Epic",
            TenantId = tenantId
        };

        _mockClients.Setup(c => c.Groups(It.IsAny<string>(), It.IsAny<string>()))
            .Returns(_mockClientProxy.Object);

        // Act
        await _service.NotifyPendingChangeCreatedAsync(notification);

        // Assert
        _mockClients.Verify(
            c => c.Groups($"pending-change-{pendingChangeId}", $"tenant-{tenantId}"),
            Times.Once);

        _mockClientProxy.Verify(
            p => p.SendCoreAsync(
                "PendingChangeCreated",
                It.Is<object[]>(args => args.Length == 1 && args[0] == notification),
                It.IsAny<CancellationToken>()),
            Times.Once);
    }

    [Fact]
    public async Task NotifyPendingChangeApprovedAsync_SendsNotificationWithCorrectData()
    {
        // Arrange
        var pendingChangeId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var approvedBy = Guid.NewGuid();
        var entityId = Guid.NewGuid();

        var notification = new PendingChangeApprovedNotification
        {
            NotificationType = "PendingChangeApproved",
            PendingChangeId = pendingChangeId,
            ToolName = "create_epic",
            EntityType = "Epic",
            Operation = "CREATE",
            EntityId = entityId,
            ApprovedBy = approvedBy,
            ExecutionResult = "Epic created: Test Epic",
            TenantId = tenantId
        };

        _mockClients.Setup(c => c.Groups(It.IsAny<string>(), It.IsAny<string>()))
            .Returns(_mockClientProxy.Object);

        // Act
        await _service.NotifyPendingChangeApprovedAsync(notification);

        // Assert
        _mockClientProxy.Verify(
            p => p.SendCoreAsync(
                "PendingChangeApproved",
                It.Is<object[]>(args =>
                    args.Length == 1 &&
                    ((PendingChangeApprovedNotification)args[0]).ApprovedBy == approvedBy &&
                    ((PendingChangeApprovedNotification)args[0]).EntityId == entityId),
                It.IsAny<CancellationToken>()),
            Times.Once);
    }

    [Fact]
    public async Task NotifyPendingChangeRejectedAsync_SendsNotificationWithReason()
    {
        // Arrange
        var pendingChangeId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var rejectedBy = Guid.NewGuid();
        var reason = "Epic name is too vague";

        var notification = new PendingChangeRejectedNotification
        {
            NotificationType = "PendingChangeRejected",
            PendingChangeId = pendingChangeId,
            ToolName = "create_epic",
            Reason = reason,
            RejectedBy = rejectedBy,
            TenantId = tenantId
        };

        _mockClients.Setup(c => c.Groups(It.IsAny<string>(), It.IsAny<string>()))
            .Returns(_mockClientProxy.Object);

        // Act
        await _service.NotifyPendingChangeRejectedAsync(notification);

        // Assert
        _mockClientProxy.Verify(
            p => p.SendCoreAsync(
                "PendingChangeRejected",
                It.Is<object[]>(args =>
                    args.Length == 1 &&
                    ((PendingChangeRejectedNotification)args[0]).Reason == reason &&
                    ((PendingChangeRejectedNotification)args[0]).RejectedBy == rejectedBy),
                It.IsAny<CancellationToken>()),
            Times.Once);
    }

    [Fact]
    public async Task NotifyPendingChangeAppliedAsync_SendsNotificationWithResult()
    {
        // Arrange
        var pendingChangeId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();
        var result = "Epic created: abc123 - Test Epic";

        var notification = new PendingChangeAppliedNotification
        {
            NotificationType = "PendingChangeApplied",
            PendingChangeId = pendingChangeId,
            ToolName = "create_epic",
            Result = result,
            AppliedAt = DateTime.UtcNow,
            TenantId = tenantId
        };

        _mockClients.Setup(c => c.Groups(It.IsAny<string>(), It.IsAny<string>()))
            .Returns(_mockClientProxy.Object);

        // Act
        await _service.NotifyPendingChangeAppliedAsync(notification);

        // Assert
        _mockClientProxy.Verify(
            p => p.SendCoreAsync(
                "PendingChangeApplied",
                It.Is<object[]>(args =>
                    args.Length == 1 &&
                    ((PendingChangeAppliedNotification)args[0]).Result == result),
                It.IsAny<CancellationToken>()),
            Times.Once);
    }

    [Fact]
    public async Task NotifyPendingChangeExpiredAsync_SendsNotificationToCorrectGroups()
    {
        // Arrange
        var pendingChangeId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();

        var notification = new PendingChangeExpiredNotification
        {
            NotificationType = "PendingChangeExpired",
            PendingChangeId = pendingChangeId,
            ToolName = "create_epic",
            ExpiredAt = DateTime.UtcNow,
            TenantId = tenantId
        };

        _mockClients.Setup(c => c.Groups(It.IsAny<string>(), It.IsAny<string>()))
            .Returns(_mockClientProxy.Object);

        // Act
        await _service.NotifyPendingChangeExpiredAsync(notification);

        // Assert
        _mockClients.Verify(
            c => c.Groups($"pending-change-{pendingChangeId}", $"tenant-{tenantId}"),
            Times.Once);

        _mockClientProxy.Verify(
            p => p.SendCoreAsync(
                "PendingChangeExpired",
                It.Is<object[]>(args => args.Length == 1),
                It.IsAny<CancellationToken>()),
            Times.Once);
    }

    [Fact]
    public void Constructor_ThrowsArgumentNullException_WhenHubContextIsNull()
    {
        // Act & Assert
        var exception = Assert.Throws<ArgumentNullException>(() =>
            new McpNotificationService(null!, _mockLogger.Object));

        Assert.Equal("hubContext", exception.ParamName);
    }

    [Fact]
    public void Constructor_ThrowsArgumentNullException_WhenLoggerIsNull()
    {
        // Act & Assert
        var exception = Assert.Throws<ArgumentNullException>(() =>
            new McpNotificationService(_mockHubContext.Object, null!));

        Assert.Equal("logger", exception.ParamName);
    }
}
