using MongoDB.Bson;
using MongoDB.Driver;
using QRRapidoApp.Data;
using System.Text.Json;

namespace QRRapidoApp.Services.Monitoring
{
    public class MongoDbMonitoringService : BackgroundService
    {
        private readonly ILogger<MongoDbMonitoringService> _logger;
        private readonly IConfiguration _configuration;
        private readonly IServiceProvider _serviceProvider;
        private readonly string _applicationName;
        
        // Configuration values
        private readonly int _intervalMinutes;
        private readonly long _databaseSizeWarningMB;
        private readonly long _databaseSizeErrorMB;
        private readonly long _growthRateWarningMBPerHour;
        private readonly bool _includeCollectionStats;
        private readonly List<string> _collectionsToMonitor;
        
        // Monitoring state
        private long _previousDatabaseSizeMB = 0;
        private DateTime _previousMeasurement = DateTime.UtcNow;
        private readonly Dictionary<string, CollectionSnapshot> _previousCollectionStats = new();

        public MongoDbMonitoringService(
            ILogger<MongoDbMonitoringService> logger,
            IConfiguration configuration,
            IServiceProvider serviceProvider)
        {
            _logger = logger;
            _configuration = configuration;
            _serviceProvider = serviceProvider;
            _applicationName = configuration["ApplicationName"] ?? "QRRapido";
            
            // Load configuration
            _intervalMinutes = configuration.GetValue<int>("MongoDbMonitoring:IntervalMinutes", 5);
            _databaseSizeWarningMB = configuration.GetValue<long>("MongoDbMonitoring:DatabaseSizeWarningMB", 1024);
            _databaseSizeErrorMB = configuration.GetValue<long>("MongoDbMonitoring:DatabaseSizeErrorMB", 5120);
            _growthRateWarningMBPerHour = configuration.GetValue<long>("MongoDbMonitoring:GrowthRateWarningMBPerHour", 100);
            _includeCollectionStats = configuration.GetValue<bool>("MongoDbMonitoring:IncludeCollectionStats", true);
            _collectionsToMonitor = configuration.GetSection("MongoDbMonitoring:CollectionsToMonitor").Get<List<string>>() 
                ?? new List<string> { "Users", "QRCodeHistory", "AdFreeSessions" };
            
            _logger.LogInformation("MongoDbMonitoringService initialized for {ApplicationName} - Interval: {IntervalMinutes}min, Warning: {WarningMB}MB, Error: {ErrorMB}MB",
                _applicationName, _intervalMinutes, _databaseSizeWarningMB, _databaseSizeErrorMB);
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("MongoDbMonitoringService started for {ApplicationName}", _applicationName);
            
            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    await MonitorMongoDbAsync();
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Error in MongoDbMonitoringService for {ApplicationName}", _applicationName);
                }
                
                await Task.Delay(TimeSpan.FromMinutes(_intervalMinutes), stoppingToken);
            }
            
            _logger.LogInformation("MongoDbMonitoringService stopped for {ApplicationName}", _applicationName);
        }

        private async Task MonitorMongoDbAsync()
        {
            using var scope = _serviceProvider.CreateScope();
            var context = scope.ServiceProvider.GetService<MongoDbContext>();
            
            if (context?.Database == null)
            {
                _logger.LogWarning("MongoDB context not available for monitoring in {ApplicationName} - skipping this cycle", _applicationName);
                return;
            }

            try
            {
                var now = DateTime.UtcNow;
                
                // Get database statistics
                var dbStats = await GetDatabaseStatsAsync(context);
                var collectionStats = new List<CollectionStatistics>();
                
                if (_includeCollectionStats)
                {
                    collectionStats = await GetCollectionStatsAsync(context);
                }
                
                // Calculate growth rate
                var growthSincePreviousMB = 0.0;
                var growthRateMBPerHour = 0.0;
                
                if (_previousDatabaseSizeMB > 0)
                {
                    growthSincePreviousMB = dbStats.DatabaseSizeMB - _previousDatabaseSizeMB;
                    var timeDelta = (now - _previousMeasurement).TotalHours;
                    if (timeDelta > 0)
                    {
                        growthRateMBPerHour = growthSincePreviousMB / timeDelta;
                    }
                }
                
                _previousDatabaseSizeMB = (long)dbStats.DatabaseSizeMB;
                _previousMeasurement = now;

                // Determine status
                var status = DetermineDatabaseStatus(dbStats.DatabaseSizeMB, growthRateMBPerHour);
                
                // Log structured MongoDB metrics
                using (_logger.BeginScope(new Dictionary<string, object>
                {
                    ["ApplicationName"] = _applicationName,
                    ["MongoDbMonitoring"] = true,
                    ["DatabaseName"] = dbStats.DatabaseName,
                    ["DatabaseSizeMB"] = Math.Round(dbStats.DatabaseSizeMB, 2),
                    ["DatabaseSizeGB"] = Math.Round(dbStats.DatabaseSizeMB / 1024.0, 3),
                    ["GrowthSincePreviousMB"] = Math.Round(growthSincePreviousMB, 2),
                    ["GrowthRateMBPerHour"] = Math.Round(growthRateMBPerHour, 2),
                    ["DocumentCount"] = dbStats.DocumentCount,
                    ["IndexSizeMB"] = Math.Round(dbStats.IndexSizeMB, 2),
                    ["MongoDbVersion"] = dbStats.Version,
                    ["Collections"] = collectionStats.Select(c => new
                    {
                        name = c.Name,
                        documentCount = c.DocumentCount,
                        sizeMB = Math.Round(c.SizeMB, 2),
                        indexSizeMB = Math.Round(c.IndexSizeMB, 2),
                        avgDocSizeBytes = c.AvgDocSizeBytes
                    }),
                    ["Status"] = status
                }))
                {
                    var logLevel = status switch
                    {
                        "Error" => LogLevel.Error,
                        "Warning" => LogLevel.Warning,
                        _ => LogLevel.Information
                    };

                    _logger.Log(logLevel,
                        "MongoDB monitoring - Database: {Database}, Size: {SizeMB:F1}MB ({SizeGB:F2}GB), Growth: +{GrowthMB:F1}MB ({GrowthRate:F1}MB/h), Documents: {Documents:N0}, Status: {Status}",
                        dbStats.DatabaseName, dbStats.DatabaseSizeMB, dbStats.DatabaseSizeMB / 1024.0, 
                        growthSincePreviousMB, growthRateMBPerHour, dbStats.DocumentCount, status);
                }
                
                // Check for alerts
                await CheckDatabaseAlertsAsync(dbStats, growthRateMBPerHour, collectionStats);
                
                // Update collection tracking
                UpdateCollectionTracking(collectionStats);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to monitor MongoDB for {ApplicationName}", _applicationName);
            }
        }

        private async Task<DatabaseStatistics> GetDatabaseStatsAsync(MongoDbContext context)
        {
            var command = new BsonDocument("dbStats", 1);
            var result = await context.Database!.RunCommandAsync<BsonDocument>(command);
            
            var databaseName = context.Database!.DatabaseNamespace.DatabaseName;
            var dataSize = result.GetValue("dataSize", BsonValue.Create(0)).AsDouble;
            var indexSize = result.GetValue("indexSize", BsonValue.Create(0)).AsDouble;
            var totalSize = dataSize + indexSize;
            var documentCount = result.GetValue("objects", BsonValue.Create(0)).ToInt64();
            
            // Get MongoDB version
            var serverStatus = await context.Database!.RunCommandAsync<BsonDocument>(new BsonDocument("serverStatus", 1));
            var version = serverStatus.GetValue("version", BsonValue.Create("unknown")).AsString;
            
            return new DatabaseStatistics
            {
                DatabaseName = databaseName,
                DatabaseSizeMB = totalSize / (1024 * 1024),
                IndexSizeMB = indexSize / (1024 * 1024),
                DocumentCount = documentCount,
                Version = version
            };
        }

        private async Task<List<CollectionStatistics>> GetCollectionStatsAsync(MongoDbContext context)
        {
            var collectionStats = new List<CollectionStatistics>();
            
            var collections = await context.Database!.ListCollectionNamesAsync();
            var collectionList = await collections.ToListAsync();
            
            foreach (var collectionName in collectionList.Where(c => ShouldMonitorCollection(c)))
            {
                try
                {
                    var command = new BsonDocument("collStats", collectionName);
                    var result = await context.Database!.RunCommandAsync<BsonDocument>(command);

                    var size = GetDoubleValue(result, "size");
                    var totalIndexSize = GetDoubleValue(result, "totalIndexSize");
                    var count = result.GetValue("count", BsonValue.Create(0)).ToInt64();
                    var avgObjSize = GetDoubleValue(result, "avgObjSize");

                    collectionStats.Add(new CollectionStatistics
                    {
                        Name = collectionName,
                        SizeMB = size / (1024 * 1024),
                        IndexSizeMB = totalIndexSize / (1024 * 1024),
                        DocumentCount = count,
                        AvgDocSizeBytes = (long)avgObjSize
                    });
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Failed to get stats for collection {CollectionName} in {ApplicationName}", 
                        collectionName, _applicationName);
                }
            }
            
            return collectionStats.OrderByDescending(c => c.SizeMB).ToList();
        }

        private static double GetDoubleValue(BsonDocument document, string fieldName)
        {
            var value = document.GetValue(fieldName, BsonValue.Create(0));
            return value.BsonType switch
            {
                BsonType.Double => value.AsDouble,
                BsonType.Int32 => (double)value.AsInt32,
                BsonType.Int64 => (double)value.AsInt64,
                BsonType.Decimal128 => (double)value.AsDecimal128,
                _ => 0.0
            };
        }

        private bool ShouldMonitorCollection(string collectionName)
        {
            return _collectionsToMonitor.Any(monitored => 
                string.Equals(monitored, collectionName, StringComparison.OrdinalIgnoreCase));
        }

        private string DetermineDatabaseStatus(double databaseSizeMB, double growthRateMBPerHour)
        {
            if (databaseSizeMB > _databaseSizeErrorMB)
            {
                return "Error";
            }
            
            if (databaseSizeMB > _databaseSizeWarningMB || 
                Math.Abs(growthRateMBPerHour) > _growthRateWarningMBPerHour)
            {
                return "Warning";
            }
            
            return "Healthy";
        }

        private async Task CheckDatabaseAlertsAsync(DatabaseStatistics dbStats, double growthRate, List<CollectionStatistics> collections)
        {
            // Database size alerts
            if (dbStats.DatabaseSizeMB > _databaseSizeErrorMB)
            {
                _logger.LogError("ALERT: Database size critical for {ApplicationName} - {SizeMB:F1}MB exceeds error threshold of {ThresholdMB}MB",
                    _applicationName, dbStats.DatabaseSizeMB, _databaseSizeErrorMB);
            }
            else if (dbStats.DatabaseSizeMB > _databaseSizeWarningMB)
            {
                _logger.LogWarning("ALERT: Database size warning for {ApplicationName} - {SizeMB:F1}MB exceeds warning threshold of {ThresholdMB}MB",
                    _applicationName, dbStats.DatabaseSizeMB, _databaseSizeWarningMB);
            }

            // Growth rate alerts
            if (Math.Abs(growthRate) > _growthRateWarningMBPerHour)
            {
                var growthType = growthRate > 0 ? "growth" : "shrinkage";
                _logger.LogWarning("ALERT: High database {GrowthType} rate for {ApplicationName} - {GrowthRate:F1}MB/hour exceeds threshold of {ThresholdMB}MB/hour",
                    growthType, _applicationName, Math.Abs(growthRate), _growthRateWarningMBPerHour);
            }

            // Collection-specific alerts
            foreach (var collection in collections.Take(5)) // Top 5 largest collections
            {
                if (collection.SizeMB > 100) // Alert for collections over 100MB
                {
                    _logger.LogInformation("Large collection detected in {ApplicationName} - {CollectionName}: {SizeMB:F1}MB with {DocumentCount:N0} documents",
                        _applicationName, collection.Name, collection.SizeMB, collection.DocumentCount);
                }
            }
        }

        private void UpdateCollectionTracking(List<CollectionStatistics> currentStats)
        {
            foreach (var stat in currentStats)
            {
                if (_previousCollectionStats.ContainsKey(stat.Name))
                {
                    var previous = _previousCollectionStats[stat.Name];
                    var documentGrowth = stat.DocumentCount - previous.DocumentCount;
                    var sizeGrowthMB = stat.SizeMB - previous.SizeMB;
                    
                    if (documentGrowth > 1000 || sizeGrowthMB > 10) // Significant growth
                    {
                        _logger.LogInformation("Collection growth detected in {ApplicationName} - {CollectionName}: +{DocumentGrowth} documents, +{SizeGrowthMB:F1}MB",
                            _applicationName, stat.Name, documentGrowth, sizeGrowthMB);
                    }
                }
                
                _previousCollectionStats[stat.Name] = new CollectionSnapshot
                {
                    DocumentCount = stat.DocumentCount,
                    SizeMB = stat.SizeMB,
                    LastMeasurement = DateTime.UtcNow
                };
            }
        }
    }

    public class DatabaseStatistics
    {
        public string DatabaseName { get; set; } = string.Empty;
        public double DatabaseSizeMB { get; set; }
        public double IndexSizeMB { get; set; }
        public long DocumentCount { get; set; }
        public string Version { get; set; } = string.Empty;
    }

    public class CollectionStatistics
    {
        public string Name { get; set; } = string.Empty;
        public double SizeMB { get; set; }
        public double IndexSizeMB { get; set; }
        public long DocumentCount { get; set; }
        public long AvgDocSizeBytes { get; set; }
    }

    public class CollectionSnapshot
    {
        public long DocumentCount { get; set; }
        public double SizeMB { get; set; }
        public DateTime LastMeasurement { get; set; }
    }
}