using Foxel.Models; using Foxel.Models.DataBase; using Foxel.Models.Request.Storage; using Foxel.Models.Response.Storage; using Foxel.Services.Attributes; using Foxel.Services.Storage.Providers; // Required for config types like LocalStorageConfig, etc. using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System.Text.Json; using Foxel.Services.Configuration; // Added for IConfigService namespace Foxel.Services.Management; public class StorageManagementService : IStorageManagementService { private readonly IDbContextFactory _contextFactory; private readonly ILogger _logger; private readonly IConfigService _configService; // Added IConfigService private const string DefaultStorageModeIdKey = "Storage:DefaultStorageModeId"; // Define key for config public StorageManagementService( IDbContextFactory contextFactory, ILogger logger, IConfigService configService) // Added IConfigService to constructor { _contextFactory = contextFactory; _logger = logger; _configService = configService; // Initialize IConfigService } public async Task> GetStorageModesAsync(int page = 1, int pageSize = 10, string? searchQuery = null, StorageType? storageType = null, bool? isEnabled = null) { if (page < 1) page = 1; if (pageSize < 1) pageSize = 10; await using var dbContext = await _contextFactory.CreateDbContextAsync(); var query = dbContext.StorageModes.AsQueryable(); if (!string.IsNullOrWhiteSpace(searchQuery)) { query = query.Where(sm => sm.Name.Contains(searchQuery)); } if (storageType.HasValue) { query = query.Where(sm => sm.StorageType == storageType.Value); } if (isEnabled.HasValue) { query = query.Where(sm => sm.IsEnabled == isEnabled.Value); } query = query.OrderByDescending(sm => sm.CreatedAt); var totalCount = await query.CountAsync(); var storageModes = await query .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); var responseItems = storageModes.Select(sm => new StorageModeResponse { Id = sm.Id, Name = sm.Name, StorageType = sm.StorageType, ConfigurationJson = sm.ConfigurationJson, // Consider masking sensitive info if necessary IsEnabled = sm.IsEnabled, CreatedAt = sm.CreatedAt, UpdatedAt = sm.UpdatedAt }).ToList(); return new PaginatedResult { Data = responseItems, Page = page, PageSize = pageSize, TotalCount = totalCount }; } public async Task GetStorageModeByIdAsync(int id) { await using var dbContext = await _contextFactory.CreateDbContextAsync(); var sm = await dbContext.StorageModes.FindAsync(id); if (sm == null) throw new KeyNotFoundException($"StorageMode with ID {id} not found."); return new StorageModeResponse { Id = sm.Id, Name = sm.Name, StorageType = sm.StorageType, ConfigurationJson = sm.ConfigurationJson, IsEnabled = sm.IsEnabled, CreatedAt = sm.CreatedAt, UpdatedAt = sm.UpdatedAt }; } public async Task CreateStorageModeAsync(CreateStorageModeRequest request) { await using var dbContext = await _contextFactory.CreateDbContextAsync(); ValidateConfiguration(request.StorageType, request.ConfigurationJson, request.Name); var storageMode = new Models.DataBase.StorageMode { Name = request.Name, StorageType = request.StorageType, ConfigurationJson = request.ConfigurationJson, IsEnabled = request.IsEnabled, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; dbContext.StorageModes.Add(storageMode); await dbContext.SaveChangesAsync(); return await GetStorageModeByIdAsync(storageMode.Id); // Reuse to get full response model } public async Task UpdateStorageModeAsync(UpdateStorageModeRequest request) { await using var dbContext = await _contextFactory.CreateDbContextAsync(); var storageMode = await dbContext.StorageModes.FindAsync(request.Id); if (storageMode == null) throw new KeyNotFoundException($"StorageMode with ID {request.Id} not found."); ValidateConfiguration(request.StorageType, request.ConfigurationJson, request.Name); storageMode.Name = request.Name; storageMode.StorageType = request.StorageType; storageMode.ConfigurationJson = request.ConfigurationJson; storageMode.IsEnabled = request.IsEnabled; storageMode.UpdatedAt = DateTime.UtcNow; await dbContext.SaveChangesAsync(); return await GetStorageModeByIdAsync(storageMode.Id); } public async Task DeleteStorageModeAsync(int id) { await using var dbContext = await _contextFactory.CreateDbContextAsync(); var storageMode = await dbContext.StorageModes.FindAsync(id); if (storageMode == null) throw new KeyNotFoundException($"StorageMode with ID {id} not found."); // Check if any pictures are using this storage mode bool isInUse = await dbContext.Pictures.AnyAsync(p => p.StorageModeId == id); if (isInUse) { _logger.LogWarning("Attempted to delete StorageMode ID {StorageModeId} which is currently in use by one or more pictures.", id); throw new InvalidOperationException($"StorageMode '{storageMode.Name}' (ID: {id}) cannot be deleted because it is currently in use by one or more pictures."); } dbContext.StorageModes.Remove(storageMode); await dbContext.SaveChangesAsync(); return true; } public async Task BatchDeleteStorageModesAsync(List ids) { var result = new BatchDeleteResult(); await using var dbContext = await _contextFactory.CreateDbContextAsync(); foreach (var id in ids) { var storageMode = await dbContext.StorageModes.FindAsync(id); if (storageMode == null) { result.FailedCount++; result.FailedIds.Add(id); _logger.LogWarning("Batch delete: StorageMode with ID {StorageModeId} not found.", id); continue; } bool isInUse = await dbContext.Pictures.AnyAsync(p => p.StorageModeId == id); if (isInUse) { result.FailedCount++; result.FailedIds.Add(id); _logger.LogWarning("Batch delete: StorageMode ID {StorageModeId} ('{StorageModeName}') is in use and cannot be deleted.", id, storageMode.Name); continue; } try { dbContext.StorageModes.Remove(storageMode); result.SuccessCount++; } catch (Exception ex) { result.FailedCount++; result.FailedIds.Add(id); _logger.LogError(ex, "Batch delete: Error deleting StorageMode ID {StorageModeId}.", id); } } if (result.SuccessCount > 0) { await dbContext.SaveChangesAsync(); } return result; } public Task> GetStorageTypesAsync() { var types = Enum.GetValues(typeof(StorageType)) .Cast() .Select(e => new StorageTypeResponse { Value = (int)e, Name = e.ToString() }) .ToList(); return Task.FromResult>(types); } public async Task GetDefaultStorageModeIdAsync() { var idString = await _configService.GetValueAsync(DefaultStorageModeIdKey); if (int.TryParse(idString, out var id)) { // Optionally, verify if this ID still exists and is enabled in StorageModes table await using var dbContext = await _contextFactory.CreateDbContextAsync(); var storageModeExists = await dbContext.StorageModes.AnyAsync(sm => sm.Id == id && sm.IsEnabled); if (storageModeExists) { return id; } _logger.LogWarning("Default storage mode ID {DefaultStorageModeId} from config does not exist or is not enabled.", id); // If it doesn't exist or isn't enabled, perhaps clear the setting or return null // For now, returning null as it's not a valid/usable default await _configService.DeleteConfigAsync(DefaultStorageModeIdKey); // Clean up invalid setting return null; } return null; } public async Task SetDefaultStorageModeAsync(int storageModeId) { await using var dbContext = await _contextFactory.CreateDbContextAsync(); var storageMode = await dbContext.StorageModes.FindAsync(storageModeId); if (storageMode == null) { _logger.LogWarning("Attempted to set default storage mode to a non-existent ID: {StorageModeId}", storageModeId); throw new KeyNotFoundException($"StorageMode with ID {storageModeId} not found."); } if (!storageMode.IsEnabled) { _logger.LogWarning("Attempted to set default storage mode to a disabled StorageMode ID: {StorageModeId}, Name: {StorageModeName}", storageModeId, storageMode.Name); throw new InvalidOperationException($"StorageMode '{storageMode.Name}' (ID: {storageModeId}) is disabled and cannot be set as default."); } // Validate configuration before setting as default try { ValidateConfiguration(storageMode.StorageType, storageMode.ConfigurationJson, storageMode.Name); } catch (Exception ex) { _logger.LogError(ex, "Validation failed for StorageMode ID {StorageModeId} ('{StorageModeName}') when trying to set as default. Configuration: {ConfigurationJson}", storageModeId, storageMode.Name, storageMode.ConfigurationJson); throw new InvalidOperationException($"StorageMode '{storageMode.Name}' (ID: {storageModeId}) has an invalid configuration and cannot be set as default. Error: {ex.Message}", ex); } await _configService.SetConfigAsync(DefaultStorageModeIdKey, storageModeId.ToString(), "The ID of the default storage mode to be used by the application."); _logger.LogInformation("Default storage mode set to ID: {StorageModeId}, Name: {StorageModeName}", storageModeId, storageMode.Name); return true; } private void ValidateConfiguration(StorageType storageType, string? jsonConfig, string storageModeName) { if (string.IsNullOrWhiteSpace(jsonConfig)) { // Configuration can be optional for some types or scenarios, // but if a type inherently requires config, this check might need adjustment. // For now, we assume if jsonConfig is null/empty, it's intentional. // The actual provider instantiation will fail if config is required but missing. // This validation step is more about format if config IS provided. return; } try { object? deserializedConfig = storageType switch { StorageType.Local => JsonSerializer.Deserialize(jsonConfig), StorageType.Telegram => JsonSerializer.Deserialize(jsonConfig), StorageType.S3 => JsonSerializer.Deserialize(jsonConfig), StorageType.Cos => JsonSerializer.Deserialize(jsonConfig), StorageType.WebDAV => JsonSerializer.Deserialize(jsonConfig), _ => throw new NotSupportedException($"StorageType {storageType} configuration validation is not supported.") }; if (deserializedConfig == null) { throw new JsonException($"Unable to deserialize configuration for {storageType}. JSON: {jsonConfig}"); } // Further property-level validation can be added here if needed (e.g., checking required fields within the config object) } catch (JsonException ex) { _logger.LogError(ex, "Invalid JSON configuration for StorageMode '{StorageModeName}' (Type: {StorageType}). JSON: {JsonConfig}", storageModeName, storageType, jsonConfig); throw new ArgumentException($"Configuration for StorageMode '{storageModeName}' (Type: {storageType}) is invalid: {ex.Message}", nameof(jsonConfig), ex); } catch (NotSupportedException ex) { _logger.LogError(ex, "Validation not supported for StorageType '{StorageType}' in StorageMode '{StorageModeName}'.", storageType, storageModeName); throw new ArgumentException(ex.Message, nameof(storageType), ex); } } }