using Application.Abstractions.Interfaces; using Application.DTOs.AdminDTOs; using Entities.Interfaces; using Entities.Models; using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace Application.Services { public class AdminService : IAdminService { private readonly IUserRepository _userRepository; private readonly IOrderRepository _orderRepository; private readonly IRobotRepository _robotRepository; private readonly INodeRepository _nodeRepository; private readonly IAdminKeyRepository _adminKeyRepository; private readonly Entities.Config.Config _config; public AdminService( IUserRepository userRepository, IOrderRepository orderRepository, IRobotRepository robotRepository, INodeRepository nodeRepository, IAdminKeyRepository adminKeyRepository, Entities.Config.Config config) { _userRepository = userRepository; _orderRepository = orderRepository; _robotRepository = robotRepository; _nodeRepository = nodeRepository; _adminKeyRepository = adminKeyRepository; _config = config; } public async Task GetSystemStatsAsync() { var users = await _userRepository.GetAllAsync(); var orders = await _orderRepository.GetAllAsync(); var robots = await _robotRepository.GetAllAsync(); var nodes = await _nodeRepository.GetAllAsync(); var ordersList = orders.ToList(); var robotsList = robots.ToList(); var stats = new SystemStatsDTO { TotalUsers = users.Count(), TotalOrders = ordersList.Count, TotalRobots = robotsList.Count, TotalNodes = nodes.Count(), ActiveOrders = ordersList.Count(o => o.Status == OrderStatus.Processing || o.Status == OrderStatus.EnRoute), CompletedOrders = ordersList.Count(o => o.Status == OrderStatus.Delivered), CancelledOrders = ordersList.Count(o => o.Status == OrderStatus.Cancelled), AvailableRobots = robotsList.Count(r => r.Status == RobotStatus.Idle), BusyRobots = robotsList.Count(r => r.Status == RobotStatus.Delivering), ChargingRobots = robotsList.Count(r => r.Status == RobotStatus.Charging), AverageBatteryLevel = robotsList.Any() ? robotsList.Average(r => r.BatteryLevel) : 0, DeliveryRevenue = (double)ordersList.Where(o => o.Status == OrderStatus.Delivered).Sum(o => o.DeliveryPrice), ProductRevenue = (double)ordersList.Where(o => o.IsProductPaid && o.Status == OrderStatus.Delivered).Sum(o => o.ProductPrice), TotalRevenue = (double)ordersList.Where(o => o.IsProductPaid && o.Status == OrderStatus.Delivered).Sum(o => o.DeliveryPrice + o.ProductPrice) }; return stats; } public async Task ExportDeliveryHistoryAsync() { var orders = await _orderRepository.GetAllAsync(); var deliveryHistory = orders.Select(o => new { o.Id, o.Name, o.Description, o.Weight, o.DeliveryPrice, o.ProductPrice, o.IsProductPaid, Status = o.Status.ToString(), o.CreatedAt, o.CompletedAt, SenderId = o.SenderId, SenderName = o.Sender?.UserName, RecipientId = o.RecipientId, RecipientName = o.Recipient?.UserName, RobotId = o.RobotId, RobotName = o.AssignedRobot?.Name, PickupNodeId = o.PickupNodeId, PickupNodeName = o.PickupNode?.Name, DropoffNodeId = o.DropoffNodeId, DropoffNodeName = o.DropoffNode?.Name }).ToList(); var options = new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; return JsonSerializer.Serialize(deliveryHistory, options); } public async Task CreateDatabaseBackupAsync(string backupPath) { try { // For SQLite, create a backup by copying the database file // In production, use proper backup strategies // Try multiple possible paths string[] possiblePaths = new[] { Path.Combine(Directory.GetCurrentDirectory(), "Infrastructure", "DB_Storage", "RobDelivery.db"), Path.Combine(Directory.GetCurrentDirectory(), "..", "Infrastructure", "DB_Storage", "RobDelivery.db"), Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "Infrastructure", "DB_Storage", "RobDelivery.db") }; string? sourceDbPath = null; foreach (var path in possiblePaths) { var fullPath = Path.GetFullPath(path); if (System.IO.File.Exists(fullPath)) { sourceDbPath = fullPath; break; } } if (sourceDbPath == null || !System.IO.File.Exists(sourceDbPath)) { throw new FileNotFoundException($"Database file not found. Tried paths: {string.Join(", ", possiblePaths.Select(Path.GetFullPath))}"); } string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string backupFileName = $"RobDelivery_Backup_{timestamp}.db"; string fullBackupPath = Path.Combine(backupPath, backupFileName); // Ensure backup directory exists Directory.CreateDirectory(backupPath); // Copy database file System.IO.File.Copy(sourceDbPath, fullBackupPath, overwrite: true); // Also export delivery history as JSON string historyJson = await ExportDeliveryHistoryAsync(); string jsonBackupPath = Path.Combine(backupPath, $"DeliveryHistory_{timestamp}.json"); await System.IO.File.WriteAllTextAsync(jsonBackupPath, historyJson); return true; } catch (Exception) { return false; } } public async Task> GetRobotEfficiencyAsync() { var robots = await _robotRepository.GetAllAsync(); var efficiency = new Dictionary(); foreach (var robot in robots) { // Calculate efficiency based on completed deliveries and battery usage // Efficiency = (Number of completed orders) / (100 - Average battery level) // This is a simplified metric - in production, use more sophisticated analytics int completedOrders = robot.ActiveOrders?.Count(o => o.Status == OrderStatus.Delivered) ?? 0; double batteryEfficiency = robot.BatteryLevel > 0 ? completedOrders / (101 - robot.BatteryLevel) : 0; efficiency[robot.Id] = Math.Round(batteryEfficiency * 100, 2); } return efficiency; } public async Task GenerateAdminKeyAsync(int createdByAdminId, DateTime? expiresAt = null, string? description = null) { // Generate a unique secure key code string keyCode = GenerateSecureKeyCode(); var adminKey = new AdminKey { KeyCode = keyCode, CreatedAt = DateTime.UtcNow, ExpiresAt = expiresAt, IsUsed = false, CreatedByAdminId = createdByAdminId, Description = description }; await _adminKeyRepository.AddAsync(adminKey); await _adminKeyRepository.SaveChangesAsync(); // Reload to get navigation properties var createdKey = await _adminKeyRepository.GetByKeyCodeAsync(keyCode); return MapToDTO(createdKey!); } public async Task> GetAllAdminKeysAsync() { var keys = await _adminKeyRepository.GetAllAsync(); return keys.Select(MapToDTO); } public async Task> GetUnusedAdminKeysAsync() { var keys = await _adminKeyRepository.GetUnusedKeysAsync(); return keys.Select(MapToDTO); } public async Task RevokeAdminKeyAsync(int keyId) { var key = await _adminKeyRepository.GetByIdAsync(keyId); if (key == null || key.IsUsed) return false; key.IsUsed = true; key.UsedAt = DateTime.UtcNow; await _adminKeyRepository.UpdateAsync(key); await _adminKeyRepository.SaveChangesAsync(); return true; } private string GenerateSecureKeyCode() { // Generate a secure random key using cryptographic random const int keyLength = 32; var randomBytes = new byte[keyLength]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(randomBytes); } // Convert to base64 and make it URL-safe string keyCode = Convert.ToBase64String(randomBytes) .Replace("+", "") .Replace("/", "") .Replace("=", "") .Substring(0, 24); // Take first 24 characters for readability return $"ADMIN-{keyCode}"; } private AdminKeyDTO MapToDTO(AdminKey key) { return new AdminKeyDTO { Id = key.Id, KeyCode = key.KeyCode, CreatedAt = key.CreatedAt, ExpiresAt = key.ExpiresAt, IsUsed = key.IsUsed, UsedAt = key.UsedAt, UsedByUserId = key.UsedByUserId, UsedByUserName = key.UsedByUser?.UserName, CreatedByAdminId = key.CreatedByAdminId, CreatedByAdminName = key.CreatedByAdmin?.UserName ?? "Unknown", Description = key.Description }; } public async Task<(byte[] content, string contentType, string fileName)> DownloadDatabaseAsync() { var dbPath = GetDbPath(); if (string.IsNullOrEmpty(dbPath) || !System.IO.File.Exists(dbPath)) { throw new FileNotFoundException("Database file not found at " + dbPath); } var content = await System.IO.File.ReadAllBytesAsync(dbPath); return (content, "application/x-sqlite3", Path.GetFileName(dbPath)); } public async Task RestoreDatabaseAsync(Stream dbStream) { var dbPath = GetDbPath(); if (string.IsNullOrEmpty(dbPath)) { throw new InvalidOperationException("Could not determine database path"); } Console.WriteLine($"[AdminService] Restoring database to: {dbPath}"); // 1. Clear connection pools to release file locks try { Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools(); } catch { /* Ignored if library not present */ } // 2. Backup existingcompanion files (WAL, SHM) if they exist var companionFiles = new[] { dbPath + "-wal", dbPath + "-shm" }; foreach (var companion in companionFiles) { if (System.IO.File.Exists(companion)) { try { System.IO.File.Delete(companion); } catch { /* Might be locked */ } } } // 3. Overwrite main database file // Use a temporary file first for safety, then move var tempPath = dbPath + ".tmp"; using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None)) { await dbStream.CopyToAsync(fileStream); } // Move temp to real (force overwrite) System.IO.File.Move(tempPath, dbPath, true); Console.WriteLine("[AdminService] Database restore completed successfully."); return true; } private string GetDbPath() { var connectionString = _config.ConnectionStrings.DefaultConnection; if (string.IsNullOrEmpty(connectionString)) return string.Empty; var dataSource = connectionString.Replace("Data Source=", "").Trim(); if (Path.IsPathRooted(dataSource)) return dataSource; // Try common locations if not rooted string[] possiblePaths = new[] { Path.Combine(Directory.GetCurrentDirectory(), dataSource), Path.Combine(Directory.GetCurrentDirectory(), "Infrastructure", "DB_Storage", dataSource), Path.Combine(Directory.GetCurrentDirectory(), "..", "Infrastructure", "DB_Storage", dataSource), Path.Combine(AppContext.BaseDirectory, dataSource) }; foreach (var path in possiblePaths) { if (System.IO.File.Exists(path)) return path; } return Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), dataSource)); } public async Task DeleteAdminKeyAsync(int keyId) { var key = await _adminKeyRepository.GetByIdAsync(keyId); if (key == null) return false; await _adminKeyRepository.DeleteAsync(keyId); await _adminKeyRepository.SaveChangesAsync(); return true; } public async Task UpdateAdminKeyAsync(AdminUpdateKeyDTO updateDto) { var key = await _adminKeyRepository.GetByIdAsync(updateDto.Id); if (key == null) return false; if (updateDto.ExpiresAt.HasValue) key.ExpiresAt = updateDto.ExpiresAt.Value; if (!string.IsNullOrWhiteSpace(updateDto.Description)) key.Description = updateDto.Description; await _adminKeyRepository.UpdateAsync(key); await _adminKeyRepository.SaveChangesAsync(); return true; } } }