using System;
using System.Collections.Generic;
using System.Linq;
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.Production;
using Haoliang.Models.Models.System;

namespace Haoliang.Tests.Services
{
    public class ProductionStatisticsServiceTests
    {
        private readonly Mock<IProductionRepository> _mockProductionRepository;
        private readonly Mock<IDeviceRepository> _mockDeviceRepository;
        private readonly Mock<ISystemRepository> _mockSystemRepository;
        private readonly Mock<IAlarmRepository> _mockAlarmRepository;
        private readonly Mock<ICollectionRepository> _mockCollectionRepository;
        private readonly ProductionStatisticsService _statisticsService;

        public ProductionStatisticsServiceTests()
        {
            _mockProductionRepository = new Mock<IProductionRepository>();
            _mockDeviceRepository = new Mock<IDeviceRepository>();
            _mockSystemRepository = new Mock<ISystemRepository>();
            _mockAlarmRepository = new Mock<IAlarmRepository>();
            _mockCollectionRepository = new Mock<ICollectionRepository>();

            _statisticsService = new ProductionStatisticsService(
                _mockProductionRepository.Object,
                _mockDeviceRepository.Object,
                _mockSystemRepository.Object,
                _mockAlarmRepository.Object,
                _mockCollectionRepository.Object
            );
        }

        [Fact]
        public async Task CalculateProductionTrendsAsync_ValidDeviceAndDates_ReturnsTrendAnalysis()
        {
            // Arrange
            var deviceId = 1;
            var startDate = DateTime.Now.AddDays(-7);
            var endDate = DateTime.Now;
            
            var device = new CNCDevice { Id = deviceId, Name = "Test Device" };
            var productionRecords = new List<ProductionRecord>
            {
                new ProductionRecord { Id = 1, DeviceId = deviceId, Created = startDate, Quantity = 100, TargetQuantity = 120 },
                new ProductionRecord { Id = 2, DeviceId = deviceId, Created = startDate.AddDays(1), Quantity = 110, TargetQuantity = 120 },
                new ProductionRecord { Id = 3, DeviceId = deviceId, Created = startDate.AddDays(2), Quantity = 130, TargetQuantity = 120 }
            };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
                .ReturnsAsync(device);
            _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(deviceId, startDate, endDate))
                .ReturnsAsync(productionRecords);
            _mockSystemRepository.Setup(repo => repo.GetSystemConfigurationAsync())
                .ReturnsAsync(new SystemConfiguration { DailyProductionTarget = 100 });

            // Act
            var result = await _statisticsService.CalculateProductionTrendsAsync(deviceId, startDate, endDate);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(deviceId, result.DeviceId);
            Assert.Equal(device.Name, result.DeviceName);
            Assert.Equal(startDate, result.PeriodStart);
            Assert.Equal(endDate, result.PeriodEnd);
            Assert.Equal(340, result.TotalProduction);
            Assert.Equal(3, result.AverageDailyProduction);
            Assert.Equal(3, result.DailyData.Count);
        }

        [Fact]
        public async Task CalculateProductionTrendsAsync_InvalidDevice_ThrowsKeyNotFoundException()
        {
            // Arrange
            var deviceId = 999;
            var startDate = DateTime.Now.AddDays(-7);
            var endDate = DateTime.Now;

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

            // Act & Assert
            await Assert.ThrowsAsync<KeyNotFoundException>(() =>
                _statisticsService.CalculateProductionTrendsAsync(deviceId, startDate, endDate));
        }

        [Fact]
        public async Task GenerateProductionReportAsync_ValidFilter_ReturnsProductionReport()
        {
            // Arrange
            var filter = new ReportFilter
            {
                DeviceIds = new List<int> { 1 },
                StartDate = DateTime.Now.AddDays(-7),
                EndDate = DateTime.Now,
                ReportType = ReportType.Daily
            };

            var device = new CNCDevice { Id = 1, Name = "Test Device" };
            var records = new List<ProductionRecord>
            {
                new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now, Quantity = 100, TargetQuantity = 120, IsGood = true },
                new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now, Quantity = 50, TargetQuantity = 60, IsGood = true }
            };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
                .ReturnsAsync(device);
            _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByFilterAsync(filter))
                .ReturnsAsync(records);
            _mockSystemRepository.Setup(repo => repo.GetSystemConfigurationAsync())
                .ReturnsAsync(new SystemConfiguration { DailyProductionTarget = 100 });

            // Act
            var result = await _statisticsService.GenerateProductionReportAsync(filter);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(ReportType.Daily, result.ReportType);
            Assert.Equal(2, result.SummaryItems.Count);
            
            var summary = result.SummaryItems.First();
            Assert.Equal(1, summary.DeviceId);
            Assert.Equal("Test Device", summary.DeviceName);
            Assert.Equal(150, summary.Quantity);
            Assert.Equal(180, summary.TargetQuantity);
            Assert.Equal(83.33m, summary.Efficiency);
        }

        [Fact]
        public async Task GetDashboardSummaryAsync_ValidFilter_ReturnsDashboardSummary()
        {
            // Arrange
            var filter = new DashboardFilter
            {
                Date = DateTime.Today,
                IncludeAlerts = true
            };

            var device = new CNCDevice { Id = 1, Name = "Test Device" };
            var deviceSummaries = new List<DeviceSummary>
            {
                new DeviceSummary { DeviceId = 1, DeviceName = "Test Device", TodayProduction = 100, Efficiency = 85, QualityRate = 98 }
            };

            var alertSummaries = new List<AlertSummary>
            {
                new AlertSummary { AlertId = 1, DeviceName = "Test Device", AlertType = AlertType.DeviceOffline, Message = "Device offline" }
            };

            _mockDeviceRepository.Setup(repo => repo.GetAllDevicesAsync())
                .ReturnsAsync(new List<CNCDevice> { device });
            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
                .ReturnsAsync(device);
            _mockCollectionRepository.Setup(repo => GetDeviceCurrentStatusAsync(1))
                .ReturnsAsync(new DeviceCurrentStatus { Status = DeviceStatus.Online, Runtime = TimeSpan.FromHours(8) });
            _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateAsync(1, DateTime.Today))
                .ReturnsAsync(new List<ProductionRecord>());
            _mockAlarmRepository.Setup(repo => GetActiveAlertsByDeviceAsync(1))
                .ReturnsAsync(new List<Alert> { new Alert { Id = 1, DeviceId = 1, AlertType = "DeviceOffline", Message = "Device offline" } });

            // Act
            var result = await _statisticsService.GetDashboardSummaryAsync(filter);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(DateTime.Now.Date, result.GeneratedAt.Date);
            Assert.Equal(1, result.TotalDevices);
            Assert.Equal(1, result.ActiveDevices);
            Assert.Equal(0, result.OfflineDevices);
            Assert.Equal(1, result.DeviceSummaries.Count);
            Assert.Equal(1, result.ActiveAlerts.Count);
        }

        [Fact]
        public async Task CalculateOeeAsync_ValidDeviceAndDate_ReturnsOeeMetrics()
        {
            // Arrange
            var deviceId = 1;
            var date = DateTime.Today;
            
            var device = new CNCDevice { Id = deviceId, Name = "Test Device", IdealCycleTime = 2 };
            var records = new List<ProductionRecord>
            {
                new ProductionRecord { Id = 1, DeviceId = deviceId, Created = date, Quantity = 50, TargetQuantity = 60, IsGood = true }
            };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(deviceId))
                .ReturnsAsync(device);
            _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateAsync(deviceId, date))
                .ReturnsAsync(records);
            _mockSystemRepository.Setup(repo => repo.GetSystemConfigurationAsync())
                .ReturnsAsync(new SystemConfiguration { DailyWorkingHours = TimeSpan.FromHours(8) });
            _mockCollectionRepository.Setup(repo => GetDeviceStatusHistoryAsync(deviceId, date, date))
                .ReturnsAsync(new List<DeviceStatusHistory>());

            // Act
            var result = await _statisticsService.CalculateOeeAsync(deviceId, date);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(deviceId, result.DeviceId);
            Assert.Equal(device.Name, result.DeviceName);
            Assert.Equal(date, result.Date);
            Assert.True(result.Availability >= 0 && result.Availability <= 100);
            Assert.True(result.Performance >= 0 && result.Performance <= 100);
            Assert.True(result.Quality >= 0 && result.Quality <= 100);
            Assert.True(result.Oee >= 0 && result.Oee <= 100);
        }

        [Fact]
        public async Task GenerateProductionForecastAsync_ValidFilter_ReturnsProductionForecast()
        {
            // Arrange
            var filter = new ForecastFilter
            {
                DeviceId = 1,
                DaysToForecast = 7,
                Model = ForecastModel.Linear,
                HistoricalDataStart = DateTime.Now.AddDays(-30),
                HistoricalDataEnd = DateTime.Now
            };

            var device = new CNCDevice { Id = 1, Name = "Test Device" };
            var historicalData = new List<ProductionRecord>
            {
                new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now.AddDays(-30), Quantity = 100 },
                new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now.AddDays(-29), Quantity = 110 },
                new ProductionRecord { Id = 3, DeviceId = 1, Created = DateTime.Now.AddDays(-28), Quantity = 105 }
            };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
                .ReturnsAsync(device);
            _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(1, filter.HistoricalDataStart, filter.HistoricalDataEnd))
                .ReturnsAsync(historicalData);

            // Act
            var result = await _statisticsService.GenerateProductionForecastAsync(filter);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(filter.DeviceId, result.DeviceId);
            Assert.Equal(device.Name, result.DeviceName);
            Assert.Equal(filter.Model, result.ModelUsed);
            Assert.Equal(7, result.DailyForecasts.Count);
            
            // Check that forecasts have reasonable values
            foreach (var forecast in result.DailyForecasts)
            {
                Assert.True(forecast.ForecastedQuantity >= 0);
                Assert.True(forecast.ConfidenceLower <= forecast.ForecastedQuantity);
                Assert.True(forecast.ConfidenceUpper >= forecast.ForecastedQuantity);
            }
        }

        [Fact]
        public async Task DetectProductionAnomaliesAsync_ValidFilter_ReturnsAnomalyAnalysis()
        {
            // Arrange
            var filter = new AnomalyFilter
            {
                DeviceIds = new List<int> { 1 },
                StartDate = DateTime.Now.AddDays(-7),
                EndDate = DateTime.Now,
                MinSeverity = AnomalySeverity.Medium
            };

            var device = new CNCDevice { Id = 1, Name = "Test Device" };
            var records = new List<ProductionRecord>
            {
                new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now.AddDays(-2), Quantity = 100 },
                new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now.AddDays(-1), Quantity = 30 }, // Big drop
                new ProductionRecord { Id = 3, DeviceId = 1, Created = DateTime.Now, Quantity = 95 }
            };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
                .ReturnsAsync(device);
            _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(1, filter.StartDate, filter.EndDate))
                .ReturnsAsync(records);

            // Act
            var result = await _statisticsService.DetectProductionAnomaliesAsync(filter);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(1, result.DeviceIds.Count);
            Assert.True(result.Anomalies.Count >= 1); // Should detect the production drop
            
            var anomaly = result.Anomalies.FirstOrDefault(a => a.Type == AnomalyType.ProductionDrop);
            Assert.NotNull(anomaly);
            Assert.Equal(AnomalySeverity.High, anomaly.Severity);
        }

        [Fact]
        public async Task GetEfficiencyMetricsAsync_ValidFilter_ReturnsEfficiencyMetrics()
        {
            // Arrange
            var filter = new EfficiencyFilter
            {
                DeviceIds = new List<int> { 1 },
                StartDate = DateTime.Now.AddDays(-7),
                EndDate = DateTime.Now,
                Metrics = EfficiencyMetric.Oee
            };

            var device = new CNCDevice { Id = 1, Name = "Test Device" };
            var records = new List<ProductionRecord>
            {
                new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now.AddDays(-1), Quantity = 100, TargetQuantity = 120, IsGood = true }
            };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
                .ReturnsAsync(device);
            _mockDeviceRepository.Setup(repo => repo.GetAllActiveDevicesAsync())
                .ReturnsAsync(new List<CNCDevice> { device });
            _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByDeviceAndDateRangeAsync(1, filter.StartDate, filter.EndDate))
                .ReturnsAsync(records);

            // Act
            var result = await _statisticsService.CalculateEfficiencyMetricsAsync(filter);

            // Assert
            Assert.NotNull(result);
            Assert.Single(result.DeviceIds);
            Assert.Equal(1, result.DeviceIds.First());
            Assert.True(result.Availability >= 0 && result.Availability <= 100);
            Assert.True(result.Performance >= 0 && result.Performance <= 100);
            Assert.True(result.Quality >= 0 && result.Quality <= 100);
            Assert.True(result.Oee >= 0 && result.Oee <= 100);
        }

        [Fact]
        public async Task PerformQualityAnalysisAsync_ValidFilter_ReturnsQualityAnalysis()
        {
            // Arrange
            var filter = new QualityFilter
            {
                DeviceIds = new List<int> { 1 },
                StartDate = DateTime.Now.AddDays(-7),
                EndDate = DateTime.Now,
                MetricType = QualityMetricType.DefectRate
            };

            var device = new CNCDevice { Id = 1, Name = "Test Device" };
            var records = new List<ProductionRecord>
            {
                new ProductionRecord { Id = 1, DeviceId = 1, Created = DateTime.Now, Quantity = 100, TargetQuantity = 120, IsGood = true },
                new ProductionRecord { Id = 2, DeviceId = 1, Created = DateTime.Now, Quantity = 20, TargetQuantity = 30, IsGood = false } // Defects
            };

            _mockDeviceRepository.Setup(repo => repo.GetByIdAsync(1))
                .ReturnsAsync(device);
            _mockDeviceRepository.Setup(repo => repo.GetAllActiveDevicesAsync())
                .ReturnsAsync(new List<CNCDevice> { device });
            _mockProductionRepository.Setup(repo => repo.GetProductionRecordsByFilterAsync(It.IsAny<ReportFilter>()))
                .ReturnsAsync(records);

            // Act
            var result = await _statisticsService.PerformQualityAnalysisAsync(filter);

            // Assert
            Assert.NotNull(result);
            Assert.Single(result.DeviceIds);
            Assert.Equal(1, result.TotalProduced);
            Assert.Equal(0.8m, result.QualityRate); // 80% quality rate
            Assert.Equal(0.2m, result.DefectRate); // 20% defect rate
            Assert.True(result.QualityMetrics.Count > 0);
        }
    }
}