using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Moq;
using Haoliang.Core.Services;
using Haoliang.Data.Repositories;
using Haoliang.Models.Models.Device;
using Haoliang.Models.Models.System;

namespace Haoliang.Tests.Services
{
    public class DeviceStateMachineTests
    {
        private readonly Mock<IDeviceRepository> _mockDeviceRepository;
        private readonly Mock<IDeviceCollectionService> _mockCollectionService;
        private readonly Mock<IAlarmService> _mockAlarmService;
        private readonly Mock<ICacheService> _mockCacheService;
        private readonly DeviceStateMachine _deviceStateMachine;

        public DeviceStateMachineTests()
        {
            _mockDeviceRepository = new Mock<IDeviceRepository>();
            _mockCollectionService = new Mock<IDeviceCollectionService>();
            _mockAlarmService = new Mock<IAlarmService>();
            _mockCacheService = new Mock<ICacheService>();

            _deviceStateMachine = new DeviceStateMachine(
                _mockDeviceRepository.Object,
                _mockCollectionService.Object,
                _mockAlarmService.Object,
                _mockCacheService.Object
            );
        }

        [Fact]
        public async Task GetCurrentState_DeviceInCache_ReturnsState()
        {
            // Arrange
            var deviceId = 1;
            var expectedState = DeviceState.Running;

            // Setup device context in cache
            var deviceContext = new DeviceStateContext
            {
                CurrentState = expectedState,
                PreviousState = DeviceState.Idle,
                StateChangedAt = DateTime.UtcNow
            };

            // This would require mocking the internal dictionary
            // For this test, we'll simulate the initial state
            await _deviceStateMachine.StartAsync(CancellationToken.None);
            await _deviceStateMachine.ForceStateAsync(deviceId, expectedState, "Test setup");

            // Act
            var result = _deviceStateMachine.GetCurrentState(deviceId);

            // Assert
            Assert.Equal(expectedState, result);
        }

        [Fact]
        public async Task CanTransitionAsync_ValidTransition_ReturnsTrue()
        {
            // Arrange
            var deviceId = 1;
            var fromState = DeviceState.Idle;
            var targetState = DeviceState.Running;

            // Initialize device state
            await _deviceStateMachine.ForceStateAsync(deviceId, fromState, "Test setup");

            // Act
            var result = await _deviceStateMachine.CanTransitionAsync(deviceId, targetState);

            // Assert
            Assert.True(result);
        }

        [Fact]
        public async Task CanTransitionAsync_InvalidTransition_ReturnsFalse()
        {
            // Arrange
            var deviceId = 1;
            var fromState = DeviceState.Running;
            var targetState = DeviceState.Offline;

            // Initialize device state
            await _deviceStateMachine.ForceStateAsync(deviceId, fromState, "Test setup");

            // Act
            var result = await _deviceStateMachine.CanTransitionAsync(deviceId, targetState);

            // Assert
            Assert.False(result);
        }

        [Fact]
        public async Task TransitionToStateAsync_ValidTransition_TransitionsState()
        {
            // Arrange
            var deviceId = 1;
            var fromState = DeviceState.Idle;
            var targetState = DeviceState.Running;

            var device = new CNCDevice { Id = deviceId, Name = "Test Device", Status = DeviceStatus.Idle };
            var currentStatus = new DeviceCurrentStatus { DeviceId = deviceId, Status = DeviceStatus.Idle, Runtime = TimeSpan.Zero };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
                .ReturnsAsync(device);
            _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(deviceId))
                .ReturnsAsync(currentStatus);
            _mockDeviceRepository.Setup(repo => repo.UpdateDeviceAsync(device))
                .Returns(Task.CompletedTask);

            // Initialize device state
            await _deviceStateMachine.ForceStateAsync(deviceId, fromState, "Test setup");

            // Act
            var result = await _deviceStateMachine.TransitionToStateAsync(deviceId, targetState);

            // Assert
            Assert.True(result.Success);
            Assert.Equal(fromState, result.FromState);
            Assert.Equal(targetState, result.ToState);
            Assert.Equal($"Successfully transitioned from {fromState} to {targetState}", result.Message);
        }

        [Fact]
        public async Task TransitionToStateAsync_InvalidTransition_ReturnsFailure()
        {
            // Arrange
            var deviceId = 1;
            var fromState = DeviceState.Running;
            var targetState = DeviceState.Offline;

            var device = new CNCDevice { Id = deviceId, Name = "Test Device" };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
                .ReturnsAsync(device);

            // Initialize device state
            await _deviceStateMachine.ForceStateAsync(deviceId, fromState, "Test setup");

            // Act
            var result = await _deviceStateMachine.TransitionToStateAsync(deviceId, targetState);

            // Assert
            Assert.False(result.Success);
            Assert.Contains($"Cannot transition from {fromState} to {targetState}", result.Message);
        }

        [Fact]
        public async Task TriggerEventAsync_ValidEvent_TransitionsState()
        {
            // Arrange
            var deviceId = 1;
            var deviceEvent = DeviceEvent.Start;
            var targetState = DeviceState.Running;

            var device = new CNCDevice { Id = deviceId, Name = "Test Device", EnableProduction = true };
            var currentStatus = new DeviceCurrentStatus { DeviceId = deviceId, Status = DeviceStatus.Online, Runtime = TimeSpan.Zero };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
                .ReturnsAsync(device);
            _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(deviceId))
                .ReturnsAsync(currentStatus);
            _mockDeviceRepository.Setup(repo => repo.UpdateDeviceAsync(device))
                .Returns(Task.CompletedTask);

            // Initialize device state
            await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Idle, "Test setup");

            // Act
            var result = await _deviceStateMachine.TriggerEventAsync(deviceId, deviceEvent);

            // Assert
            Assert.True(result.Success);
            Assert.Equal(DeviceState.Idle, result.FromState);
            Assert.Equal(targetState, result.ToState);
        }

        [Fact]
        public async Task TriggerEventAsync_InvalidEvent_ReturnsFailure()
        {
            // Arrange
            var deviceId = 1;
            var deviceEvent = DeviceEvent.Stop; // Cannot stop when not running

            var device = new CNCDevice { Id = deviceId, Name = "Test Device" };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
                .ReturnsAsync(device);

            // Initialize device state
            await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Idle, "Test setup");

            // Act
            var result = await _deviceStateMachine.TriggerEventAsync(deviceId, deviceEvent);

            // Assert
            Assert.False(result.Success);
            Assert.Contains($"No event handler configured for {deviceEvent} in state DeviceState.Idle", result.Message);
        }

        [Fact]
        public async Task ForceStateAsync_ValidForce_TransitionsState()
        {
            // Arrange
            var deviceId = 1;
            var targetState = DeviceState.Running;
            var reason = "Emergency override";

            var device = new CNCDevice { Id = deviceId, Name = "Test Device" };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
                .ReturnsAsync(device);

            // Act
            var result = await _deviceStateMachine.ForceStateAsync(deviceId, targetState, reason);

            // Assert
            Assert.True(result.Success);
            Assert.Equal($"Forced state transition to {targetState}: {reason}", result.Message);
        }

        [Fact]
        public async Task ValidateStateAsync_ValidState_ReturnsValid()
        {
            // Arrange
            var deviceId = 1;
            var device = new CNCDevice { 
                Id = deviceId, 
                Name = "Test Device", 
                Status = DeviceStatus.Idle,
                EnableProduction = true 
            };
            var currentStatus = new DeviceCurrentStatus { 
                DeviceId = deviceId, 
                Status = DeviceStatus.Idle, 
                Runtime = TimeSpan.FromHours(1) 
            };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
                .ReturnsAsync(device);
            _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(deviceId))
                .ReturnsAsync(currentStatus);

            // Initialize device state
            await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Idle, "Test setup");

            // Act
            var result = await _deviceStateMachine.ValidateStateAsync(deviceId);

            // Assert
            Assert.True(result.IsValid);
            Assert.Equal(DeviceState.Idle, result.CurrentState);
            Assert.Empty(result.Issues);
        }

        [Fact]
        public async Task ValidateStateAsync_StateMismatch_ReturnsInvalid()
        {
            // Arrange
            var deviceId = 1;
            var device = new CNCDevice { 
                Id = deviceId, 
                Name = "Test Device", 
                Status = DeviceStatus.Running, // Status doesn't match state
                EnableProduction = true 
            };
            var currentStatus = new DeviceCurrentStatus { 
                DeviceId = deviceId, 
                Status = DeviceStatus.Running, 
                Runtime = TimeSpan.FromHours(1) 
            };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
                .ReturnsAsync(device);
            _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(deviceId))
                .ReturnsAsync(currentStatus);

            // Initialize device state as Idle but status is Running
            await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Idle, "Test setup");

            // Act
            var result = await _deviceStateMachine.ValidateStateAsync(deviceId);

            // Assert
            Assert.False(result.IsValid);
            Assert.Contains("State mismatch", result.Issues.First());
        }

        [Fact]
        public void RegisterStateHandler_ValidHandler_RegistersHandler()
        {
            // Arrange
            var handlerMock = new Mock<Func<int, DeviceState, DeviceState, Task>>();

            // Act
            _deviceStateMachine.RegisterStateHandler((deviceId, fromState, toState) => handlerMock.Object(deviceId, fromState, toState));

            // Assert
            // This test would need to verify the handler was registered
            // For now, we'll just ensure it doesn't throw
            Assert.True(true);
        }

        [Fact]
        public void UnregisterStateHandler_ValidHandler_UnregistersHandler()
        {
            // Arrange
            var handlerMock = new Mock<Func<int, DeviceState, DeviceState, Task>>();

            // Act
            _deviceStateMachine.RegisterStateHandler((deviceId, fromState, toState) => handlerMock.Object(deviceId, fromState, toState));
            _deviceStateMachine.UnregisterStateHandler((deviceId, fromState, toState) => handlerMock.Object(deviceId, fromState, toState));

            // Assert
            // This test would need to verify the handler was unregistered
            // For now, we'll just ensure it doesn't throw
            Assert.True(true);
        }

        [Fact]
        public async Task GetStateHistoryAsync_DeviceWithHistory_ReturnsHistory()
        {
            // Arrange
            var deviceId = 1;
            var history = new List<DeviceStateHistory>
            {
                new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Idle, ChangedAt = DateTime.Now.AddHours(-2) },
                new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Running, ChangedAt = DateTime.Now.AddHours(-1) }
            };

            _mockDeviceRepository.Setup(repo => repo.GetDeviceStateHistoryAsync(deviceId, null, null))
                .ReturnsAsync(history);

            // Initialize current state
            await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Stopped, "Test setup");

            // Act
            var result = await _deviceStateMachine.GetStateHistoryAsync(deviceId);

            // Assert
            Assert.Equal(3, result.Count); // History entries + current state
            Assert.Equal(DeviceState.Idle, result[0].State);
            Assert.Equal(DeviceState.Running, result[1].State);
            Assert.Equal(DeviceState.Stopped, result[2].State);
        }

        [Fact]
        public async Task GetStateStatisticsAsync_DeviceWithHistory_ReturnsStatistics()
        {
            // Arrange
            var deviceId = 1;
            var history = new List<DeviceStateHistory>
            {
                new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Idle, ChangedAt = DateTime.Now.AddHours(-2) },
                new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Running, ChangedAt = DateTime.Now.AddHours(-1) }
            };

            _mockDeviceRepository.Setup(repo => repo.GetDeviceStateHistoryAsync(deviceId, null, null))
                .ReturnsAsync(history);

            // Initialize current state
            await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Stopped, "Test setup");

            // Act
            var result = await _deviceStateMachine.GetStateStatisticsAsync(deviceId);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(deviceId, result.DeviceId);
            Assert.True(result.StateTransitions > 0);
            Assert.True(result.StateDurations.ContainsKey(DeviceState.Idle));
            Assert.True(result.StateDurations.ContainsKey(DeviceState.Running));
            Assert.True(result.StateDurations.ContainsKey(DeviceState.Stopped));
            Assert.True(result.StateCounts.ContainsKey(DeviceState.Idle));
            Assert.True(result.StateCounts.ContainsKey(DeviceState.Running));
            Assert.True(result.StateCounts.ContainsKey(DeviceState.Stopped));
        }

        [Fact]
        public async Task GetStateStatisticsAsync_WithDateFilter_ReturnsFilteredStatistics()
        {
            // Arrange
            var deviceId = 1;
            var filterStartDate = DateTime.Now.AddDays(-1);
            var filterEndDate = DateTime.Now;

            var history = new List<DeviceStateHistory>
            {
                new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Idle, ChangedAt = DateTime.Now.AddDays(-2) }, // Outside filter
                new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Running, ChangedAt = DateTime.Now.AddHours(-12) }, // Within filter
                new DeviceStateHistory { DeviceId = deviceId, State = DeviceState.Stopped, ChangedAt = DateTime.Now.AddHours(-6) } // Within filter
            };

            _mockDeviceRepository.Setup(repo => repo.GetDeviceStateHistoryAsync(deviceId, filterStartDate, filterEndDate))
                .ReturnsAsync(history);

            // Initialize current state
            await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Stopped, "Test setup");

            // Act
            var result = await _deviceStateMachine.GetStateStatisticsAsync(deviceId, filterStartDate, filterEndDate);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(deviceId, result.DeviceId);
            Assert.Equal(filterStartDate, result.PeriodStart);
            Assert.Equal(filterEndDate, result.PeriodEnd);
        }

        [Fact]
        public async Task TranslateStatusToDeviceState_ValidStatus_ReturnsState()
        {
            // Arrange
            var currentStatus = new DeviceCurrentStatus { Status = DeviceStatus.Running };

            // Act
            // This method would need to be made public or tested through another method
            // For now, we'll test the translation logic through a state transition
            var device = new CNCDevice { Id = 1, Name = "Test Device" };
            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
                .ReturnsAsync(device);
            _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(1))
                .ReturnsAsync(currentStatus);

            // Initialize device state and check if it matches the status
            await _deviceStateMachine.ForceStateAsync(1, DeviceState.Unknown, "Test setup");
            var result = _deviceStateMachine.GetCurrentState(1);

            // Assert
            // The state should be Running based on the status
            Assert.Equal(DeviceState.Unknown, result); // Initially unknown until transition
        }

        [Fact]
        public async Task TranslateStateToDeviceStatus_ValidState_ReturnsStatus()
        {
            // Arrange
            var state = DeviceState.Running;

            // Act
            // This would test the reverse translation
            // We can test it by verifying the device status is updated correctly
            var device = new CNCDevice { Id = 1, Name = "Test Device" };
            
            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
                .ReturnsAsync(device);
            _mockDeviceRepository.Setup(repo => repo.UpdateDeviceAsync(device))
                .Returns(Task.CompletedTask);

            // Transition to Running state and check if device status is updated
            await _deviceStateMachine.TransitionToStateAsync(1, state);

            // Assert
            // The device status should be updated to Running
            Assert.Equal(DeviceStatus.Running, device.Status);
        }

        [Fact]
        public async Task HandleInvalidStateAsync_InvalidState_TransitionsToSafeState()
        {
            // Arrange
            var deviceId = 1;
            var validationResult = new DeviceValidationResult
            {
                DeviceId = deviceId,
                IsValid = false,
                Issues = new List<string> { "State mismatch" },
                CurrentState = DeviceState.Error
            };

            var device = new CNCDevice { Id = deviceId, Name = "Test Device" };
            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
                .ReturnsAsync(device);

            // Act
            // This method is private, so we test through ValidateStateAsync which calls it
            await _deviceStateMachine.ForceStateAsync(deviceId, DeviceState.Error, "Test setup");
            var result = await _deviceStateMachine.ValidateStateAsync(deviceId);

            // The state machine should handle the invalid state
            // We expect the state to remain Error since it's already a safe state for errors
            Assert.Equal(DeviceState.Error, _deviceStateMachine.GetCurrentState(deviceId));
        }

        [Fact]
        public async Task StartAsync_InitializesDeviceStates()
        {
            // Arrange
            var devices = new List<CNCDevice>
            {
                new CNCDevice { Id = 1, Name = "Device 1" },
                new CNCDevice { Id = 2, Name = "Device 2" }
            };

            var device1Status = new DeviceCurrentStatus { DeviceId = 1, Status = DeviceStatus.Online, Runtime = TimeSpan.Zero };
            var device2Status = new DeviceCurrentStatus { DeviceId = 2, Status = DeviceStatus.Running, Runtime = TimeSpan.FromHours(1) };

            _mockDeviceRepository.Setup(repo => repo.GetAllDevicesAsync())
                .ReturnsAsync(devices);
            _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(1))
                .ReturnsAsync(device1Status);
            _mockCollectionService.Setup(service => service.GetDeviceCurrentStatusAsync(2))
                .ReturnsAsync(device2Status);

            // Act
            await _deviceStateMachine.StartAsync(CancellationToken.None);

            // Assert
            // Allow time for async initialization
            await Task.Delay(100);
            
            // Check if states are initialized based on device status
            Assert.Equal(DeviceState.Online, _deviceStateMachine.GetCurrentState(1));
            Assert.Equal(DeviceState.Running, _deviceStateMachine.GetCurrentState(2));
        }
    }
}