using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Moq;
using Microsoft.AspNetCore.SignalR;
using Haoliang.Core.Services;
using Haoliang.Models.Models.Device;
using Haoliang.Models.Models.Production;
using Haoliang.Models.Models.System;

namespace Haoliang.Tests.Services
{
    public class RealTimeServiceTests
    {
        private readonly Mock<IHubContext<RealTimeHub>> _mockHubContext;
        private readonly Mock<IDeviceCollectionService> _mockDeviceCollectionService;
        private readonly Mock<IProductionService> _mockProductionService;
        private readonly Mock<IAlarmService> _mockAlarmService;
        private readonly Mock<ICacheService> _mockCacheService;
        private readonly RealTimeService _realTimeService;

        public RealTimeServiceTests()
        {
            _mockHubContext = new Mock<IHubContext<RealTimeHub>>();
            _mockDeviceCollectionService = new Mock<IDeviceCollectionService>();
            _mockProductionService = new Mock<IProductionService>();
            _mockAlarmService = new Mock<IAlarmService>();
            _mockCacheService = new Mock<ICacheService>();

            _realTimeService = new RealTimeService(
                _mockHubContext.Object,
                _mockDeviceCollectionService.Object,
                _mockProductionService.Object,
                _mockAlarmService.Object,
                _mockCacheService.Object
            );
        }

        [Fact]
        public async Task ConnectClientAsync_ValidConnection_ConnectsClient()
        {
            // Arrange
            var connectionId = "test-connection-id";
            var userId = "test-user-id";
            var clientType = "web";

            // Act
            await _realTimeService.ConnectClientAsync(connectionId, userId, clientType);

            // Assert
            _mockHubContext.Verify(hub => hub.Clients.Client(connectionId)
                .SendAsync("ClientConnected", 
                    It.Is<object>(o => 
                        dynamic obj = o &&
                        obj.GetType().GetProperty("ClientId").GetValue(obj) == connectionId &&
                        obj.GetType().GetProperty("UserId").GetValue(obj) == userId &&
                        obj.GetType().GetProperty("ClientType").GetValue(obj) == clientType),
                    It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task DisconnectClientAsync_ValidConnection_DisconnectsClient()
        {
            // Arrange
            var connectionId = "test-connection-id";

            // Act
            await _realTimeService.DisconnectClientAsync(connectionId);

            // Assert
            _mockHubContext.Verify(hub => hub.Clients.AllExcept(connectionId)
                .SendAsync("ClientDisconnected", 
                    It.IsAny<object>(), 
                    It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task JoinDeviceGroupAsync_ValidConnection_JoinsGroup()
        {
            // Arrange
            var connectionId = "test-connection-id";
            var deviceId = 1;
            var deviceStatus = new DeviceCurrentStatus { 
                DeviceId = deviceId, 
                Status = DeviceStatus.Online, 
                CurrentProgram = "Test Program" 
            };

            _mockDeviceCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(deviceId))
                .ReturnsAsync(deviceStatus);

            // Act
            await _realTimeService.JoinDeviceGroupAsync(connectionId, deviceId);

            // Assert
            _mockHubContext.Verify(hub => hub.Groups.AddToGroupAsync(connectionId, $"device_{deviceId}", It.IsAny<CancellationToken>()), Times.Once);
            _mockHubContext.Verify(hub => hub.Clients.Client(connectionId)
                .SendAsync("DeviceStatusUpdated", 
                    It.Is<object>(o => 
                        dynamic obj = o &&
                        obj.GetType().GetProperty("DeviceId").GetValue(obj) == deviceId),
                    It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task LeaveDeviceGroupAsync_ValidConnection_LeavesGroup()
        {
            // Arrange
            var connectionId = "test-connection-id";
            var deviceId = 1;

            // Act
            await _realTimeService.LeaveDeviceGroupAsync(connectionId, deviceId);

            // Assert
            _mockHubContext.Verify(hub => hub.Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}", It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task JoinDashboardGroupAsync_ValidConnection_JoinsGroup()
        {
            // Arrange
            var connectionId = "test-connection-id";
            var dashboardId = "dashboard-1";
            var dashboardUpdate = new DashboardUpdate { 
                Timestamp = DateTime.UtcNow,
                TotalDevices = 10,
                ActiveDevices = 8,
                TotalProductionToday = 1000
            };

            _mockCacheService.Setup(cache => cache.GetOrSetDashboardSummaryAsync(It.IsAny<DateTime>(), It.IsAny<Func<Task<DashboardSummary>>>()))
                .ReturnsAsync(new DashboardSummary { 
                    TotalDevices = 10, 
                    ActiveDevices = 8, 
                    TotalProductionToday = 1000 
                });

            // Act
            await _realTimeService.JoinDashboardGroupAsync(connectionId, dashboardId);

            // Assert
            _mockHubContext.Verify(hub => hub.Groups.AddToGroupAsync(connectionId, $"dashboard_{dashboardId}", It.IsAny<CancellationToken>()), Times.Once);
            _mockHubContext.Verify(hub => hub.Clients.Client(connectionId)
                .SendAsync("DashboardUpdated", 
                    It.Is<object>(o => 
                        dynamic obj = o &&
                        obj.GetType().GetProperty("DashboardId").GetValue(obj)?.ToString() == dashboardId),
                    It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task LeaveDashboardGroupAsync_ValidConnection_LeavesGroup()
        {
            // Arrange
            var connectionId = "test-connection-id";
            var dashboardId = "dashboard-1";

            // Act
            await _realTimeService.LeaveDashboardGroupAsync(connectionId, dashboardId);

            // Assert
            _mockHubContext.Verify(hub => hub.Groups.RemoveFromGroupAsync(connectionId, $"dashboard_{dashboardId}", It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task BroadcastDeviceStatusAsync_ValidStatus_BroadcastsToGroups()
        {
            // Arrange
            var statusUpdate = new DeviceStatusUpdate
            {
                DeviceId = 1,
                DeviceName = "Test Device",
                Status = DeviceStatus.Running,
                CurrentProgram = "Test Program",
                Runtime = TimeSpan.FromHours(1),
                Timestamp = DateTime.UtcNow
            };

            // Act
            await _realTimeService.BroadcastDeviceStatusAsync(statusUpdate);

            // Assert
            _mockHubContext.Verify(hub => hub.Clients.Group($"device_1")
                .SendAsync("DeviceStatusUpdated", 
                    It.Is<DeviceStatusUpdate>(s => s.DeviceId == 1 && s.Status == DeviceStatus.Running),
                    It.IsAny<CancellationToken>()), Times.Once);
            
            _mockHubContext.Verify(hub => hub.Clients.Group("dashboard")
                .SendAsync("DeviceStatusUpdated", 
                    It.Is<DeviceStatusUpdate>(s => s.DeviceId == 1 && s.Status == DeviceStatus.Running),
                    It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task BroadcastProductionUpdateAsync_ValidUpdate_BroadcastsToGroups()
        {
            // Arrange
            var productionUpdate = new ProductionUpdate
            {
                DeviceId = 1,
                DeviceName = "Test Device",
                Quantity = 100,
                Timestamp = DateTime.UtcNow
            };

            // Act
            await _realTimeService.BroadcastProductionUpdateAsync(productionUpdate);

            // Assert
            _mockHubContext.Verify(hub => hub.Clients.Group($"device_1")
                .SendAsync("ProductionUpdated", 
                    It.Is<ProductionUpdate>(p => p.DeviceId == 1 && p.Quantity == 100),
                    It.IsAny<CancellationToken>()), Times.Once);
            
            _mockHubContext.Verify(hub => hub.Clients.Group("dashboard")
                .SendAsync("ProductionUpdated", 
                    It.Is<ProductionUpdate>(p => p.DeviceId == 1 && p.Quantity == 100),
                    It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task BroadcastAlertAsync_ValidAlert_BroadcastsToRelevantGroups()
        {
            // Arrange
            var alertUpdate = new AlertUpdate
            {
                DeviceId = 1,
                DeviceName = "Test Device",
                AlertType = "DeviceError",
                Message = "Device error occurred",
                Timestamp = DateTime.UtcNow,
                IsResolved = false
            };

            // Act
            await _realTimeService.BroadcastAlertAsync(alertUpdate);

            // Assert
            _mockHubContext.Verify(hub => hub.Clients.Group("dashboard")
                .SendAsync("AlertUpdated", 
                    It.Is<AlertUpdate>(a => a.DeviceId == 1 && a.AlertType == "DeviceError"),
                    It.IsAny<CancellationToken>()), Times.Once);
            
            _mockHubContext.Verify(hub => hub.Clients.Group("alerts")
                .SendAsync("AlertUpdated", 
                    It.Is<AlertUpdate>(a => a.DeviceId == 1 && a.AlertType == "DeviceError"),
                    It.IsAny<CancellationToken>()), Times.Once);
            
            _mockHubContext.Verify(hub => hub.Clients.Group($"device_1")
                .SendAsync("AlertUpdated", 
                    It.Is<AlertUpdate>(a => a.DeviceId == 1 && a.AlertType == "DeviceError"),
                    It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task SendSystemNotificationAsync_ValidNotification_SendsToNotificationGroup()
        {
            // Arrange
            var notification = new SystemNotification
            {
                NotificationType = "Info",
                Title = "System Update",
                Message = "System maintenance scheduled",
                Timestamp = DateTime.UtcNow
            };

            // Act
            await _realTimeService.SendSystemNotificationAsync(notification);

            // Assert
            _mockHubContext.Verify(hub => hub.Clients.Group("notifications")
                .SendAsync("SystemNotification", 
                    It.Is<SystemNotification>(n => 
                        n.NotificationType == "Info" && 
                        n.Title == "System Update"),
                    It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task SendDashboardUpdateAsync_ValidUpdate_SendsToDashboardGroup()
        {
            // Arrange
            var dashboardUpdate = new DashboardUpdate
            {
                Timestamp = DateTime.UtcNow,
                TotalDevices = 10,
                ActiveDevices = 8,
                TotalProductionToday = 1000
            };

            // Act
            await _realTimeService.SendDashboardUpdateAsync(dashboardUpdate);

            // Assert
            _mockHubContext.Verify(hub => hub.Clients.Group("dashboard")
                .SendAsync("DashboardUpdated", 
                    It.Is<DashboardUpdate>(d => 
                        d.TotalDevices == 10 && 
                        d.ActiveDevices == 8),
                    It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task SendCommandToClientAsync_ValidCommand_SendsToClient()
        {
            // Arrange
            var connectionId = "test-connection-id";
            var command = new RealTimeCommand
            {
                Command = "RefreshData",
                Parameters = new { Interval = 5000 },
                Timestamp = DateTime.UtcNow
            };

            // Act
            await _realTimeService.SendCommandToClientAsync(connectionId, command);

            // Assert
            _mockHubContext.Verify(hub => hub.Clients.Client(connectionId)
                .SendAsync("Command", 
                    It.Is<RealTimeCommand>(c => 
                        c.Command == "RefreshData" && 
                        c.Parameters.ToString().Contains("Interval")),
                    It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task BroadcastCommandAsync_ValidCommand_BroadcastsToAllClients()
        {
            // Arrange
            var command = new RealTimeCommand
            {
                Command = "SystemShutdown",
                Parameters = new { DelayMinutes = 5 },
                Timestamp = DateTime.UtcNow
            };

            // Act
            await _realTimeService.BroadcastCommandAsync(command);

            // Assert
            _mockHubContext.Verify(hub => hub.Clients.All
                .SendAsync("Command", 
                    It.Is<RealTimeCommand>(c => 
                        c.Command == "SystemShutdown"),
                    It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task GetConnectedClientsCountAsync_ValidClients_ReturnsCount()
        {
            // Arrange
            // This test would need to mock the internal client tracking
            // For now, we'll verify the method exists and doesn't throw
            var result = await _realTimeService.GetConnectedClientsCountAsync();

            // Assert
            Assert.True(result >= 0); // Should return a non-negative number
        }

        [Fact]
        public async Task GetConnectedClientsByTypeAsync_ValidType_ReturnsClients()
        {
            // Arrange
            var clientType = "web";

            // This test would need to mock the internal client tracking
            // For now, we'll verify the method exists and doesn't throw
            var result = await _realTimeService.GetConnectedClientsByTypeAsync(clientType);

            // Assert
            Assert.NotNull(result);
        }

        [Fact]
        public async Task GetDeviceMonitoringStatusAsync_ValidDevice_ReturnsStatus()
        {
            // Arrange
            var deviceId = 1;
            var streamingInfo = new DeviceStreamingInfo
            {
                DeviceId = deviceId,
                IntervalMs = 1000,
                StartedAt = DateTime.UtcNow.AddMinutes(-5),
                LastUpdate = DateTime.UtcNow.AddMinutes(-1),
                IsRunning = true
            };

            // This test would need to mock the internal device streaming tracking
            // For now, we'll verify the method exists and doesn't throw
            var result = await _realTimeService.GetDeviceMonitoringStatusAsync(deviceId);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(deviceId, result.DeviceId);
            Assert.True(result.IsStreaming);
        }

        [Fact]
        public async Task StartDeviceStreamingAsync_ValidDevice_StartsStreaming()
        {
            // Arrange
            var deviceId = 1;
            var intervalMs = 1000;

            // This test would need to mock the internal device streaming tracking
            // and verify that streaming starts
            // For now, we'll verify the method exists and doesn't throw
            await _realTimeService.StartDeviceStreamingAsync(deviceId, intervalMs);

            // Assert - would need to verify streaming started
            Assert.True(true); // Placeholder assertion
        }

        [Fact]
        public async Task StopDeviceStreamingAsync_ValidDevice_StopsStreaming()
        {
            // Arrange
            var deviceId = 1;

            // This test would need to mock the internal device streaming tracking
            // and verify that streaming stops
            // For now, we'll verify the method exists and doesn't throw
            await _realTimeService.StopDeviceStreamingAsync(deviceId);

            // Assert - would need to verify streaming stopped
            Assert.True(true); // Placeholder assertion
        }

        [Fact]
        public async Task GetActiveStreamingDevicesAsync_ReturnsStreamingDevices()
        {
            // This test would need to mock the internal device streaming tracking
            // For now, we'll verify the method exists and doesn't throw
            var result = await _realTimeService.GetActiveStreamingDevicesAsync();

            // Assert
            Assert.NotNull(result);
            Assert.IsType<List<int>>(result);
        }
    }
}