using RobotNet10.CustomConfiguration.Events; using RobotNet10.CustomConfiguration.Helpers; using RobotNet10.CustomConfiguration.Models; using RobotNet10.CustomConfiguration.Validators; using System.Text; using System.Text.Json; namespace RobotNet10.CustomConfiguration.Services; /// /// Implementation của các methods trong ConfigService /// public partial class ConfigService { public async Task CreateConfigAsync(string configType, List variables, string? description = null) { // Validate ConfigType if (string.IsNullOrWhiteSpace(configType)) { throw new ArgumentException("ConfigType cannot be empty", nameof(configType)); } // Check if ConfigType already exists var existing = await GetMetadataByTypeAsync(configType); if (existing != null) { throw new InvalidOperationException($"Config with type '{configType}' already exists"); } // Create config file var config = new ConfigFile { Id = Guid.NewGuid(), ConfigType = configType, Variables = variables, Description = description, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow, FilePath = $"{ConfigPath}/{configType}{ConfigFileExtension}" }; // Validate config var validationResult = ConfigValidator.ValidateConfig(config); if (!validationResult.IsValid) { throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}"); } // Save config file (includes metadata) await SaveConfigFileAsync(config); // Trigger event OnConfigChanged(config, ConfigChangeType.Created); return config; } public async Task GetConfigByIdAsync(Guid id) { // Load all configs and find by ID var allMetadata = await LoadAllMetadataAsync(); var metadata = allMetadata.FirstOrDefault(m => m.Id == id); if (metadata == null) { return null; } return await LoadConfigFileAsync(metadata.ConfigType); } public async Task GetConfigByTypeAsync(string configType) { return await LoadConfigFileAsync(configType); } public async Task> GetAllConfigsAsync() { var metadata = await LoadAllMetadataAsync(); return [.. metadata.OrderBy(m => m.ConfigType)]; } public async Task> SearchConfigsAsync(string? searchText) { var allMetadata = await GetAllConfigsAsync(); if (string.IsNullOrWhiteSpace(searchText)) { return allMetadata; } var searchLower = searchText.ToLower(); return [.. allMetadata.Where(m => m.ConfigType.ToLower().Contains(searchLower) || (m.Description != null && m.Description.ToLower().Contains(searchLower)) )]; } public async Task UpdateConfigAsync(Guid id, List? variables = null, string? description = null) { var config = await GetConfigByIdAsync(id) ?? throw new KeyNotFoundException($"Config with ID '{id}' not found"); // Update variables if provided if (variables != null) { config.Variables = variables; } // Update description if provided if (description != null) { config.Description = description; } config.UpdatedAt = DateTime.UtcNow; // Validate config var validationResult = ConfigValidator.ValidateConfig(config); if (!validationResult.IsValid) { throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}"); } // Save config file (includes metadata) await SaveConfigFileAsync(config); // Trigger event OnConfigChanged(config, ConfigChangeType.Updated); return config; } public async Task DeleteConfigAsync(Guid id) { // Find config by ID var allMetadata = await LoadAllMetadataAsync(); var metadata = allMetadata.FirstOrDefault(m => m.Id == id); if (metadata == null) { return false; } // Delete config file // objectName should be {configType}.config so StorageManager adds .json to make {configType}.config.json var objectName = $"{metadata.ConfigType}.config"; var exists = await _storageManager.ExistsAsync(ConfigPath, objectName, CancellationToken.None); if (exists) { await _storageManager.DeleteAsync(ConfigPath, objectName, CancellationToken.None); // Trigger event var deletedConfig = new ConfigFile { Id = metadata.Id, ConfigType = metadata.ConfigType, CreatedAt = metadata.CreatedAt, UpdatedAt = metadata.UpdatedAt, Description = metadata.Description }; OnConfigChanged(deletedConfig, ConfigChangeType.Deleted); } return true; } public async Task ConfigTypeExistsAsync(string configType) { var metadata = await GetMetadataByTypeAsync(configType); return metadata != null; } public async Task ImportConfigFromJsonAsync(Stream jsonStream, string configType) { return await ImportConfigFromJsonAsync(jsonStream, configType, null); } public async Task ImportConfigFromJsonAsync(Stream jsonStream, string configType, string? description) { // Validate ConfigType if (string.IsNullOrWhiteSpace(configType)) { throw new ArgumentException("ConfigType cannot be empty", nameof(configType)); } // Check if ConfigType already exists var existing = await GetMetadataByTypeAsync(configType); if (existing != null) { throw new InvalidOperationException($"Config with type '{configType}' already exists"); } // Try to parse as full config file (new format with metadata) // Reset stream position first jsonStream.Position = 0; string? fileDescription = null; List variables; try { // Try to parse as ConfigFile (new format) var configFile = JsonConfigParser.ParseConfigFile(jsonStream); // If successful, use description from file fileDescription = configFile.Description; variables = configFile.Variables; } catch (Exception ex) when (ex is JsonException or ArgumentException) { // If parsing as ConfigFile fails, try old format (array of variables) jsonStream.Position = 0; variables = JsonConfigParser.ParseVariables(jsonStream); } // Use description from parameter if provided, otherwise use description from file var finalDescription = !string.IsNullOrWhiteSpace(description) ? description : fileDescription; // Create config with description (from parameter or file) var config = await CreateConfigAsync(configType, variables, finalDescription); // Note: CreateConfigAsync already triggers Created event, so no need to trigger again return config; } public async Task ExportConfigToJsonAsync(Guid id) { var config = await GetConfigByIdAsync(id); return config == null ? throw new KeyNotFoundException($"Config with ID '{id}' not found") : await ExportConfigToJsonAsync(config); } public Task ExportConfigToJsonAsync(ConfigFile config) { // Export với format mới (metadata + variables) var json = JsonConfigParser.SerializeConfigFile(config); var bytes = Encoding.UTF8.GetBytes(json); Stream stream = new MemoryStream(bytes) { Position = 0 // Ensure stream is at the beginning }; return Task.FromResult(stream); } public async Task UpdateVariableAsync(Guid configId, string variableName, object? value) { var config = await GetConfigByIdAsync(configId) ?? throw new KeyNotFoundException($"Config with ID '{configId}' not found"); var variable = config.Variables.FirstOrDefault(v => v.Name.Equals(variableName, StringComparison.OrdinalIgnoreCase)) ?? throw new KeyNotFoundException($"Variable '{variableName}' not found in config"); variable.Value = value; config.UpdatedAt = DateTime.UtcNow; // Validate var validationResult = ConfigValidator.ValidateConfig(config); if (!validationResult.IsValid) { throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}"); } // Save config file (includes metadata) await SaveConfigFileAsync(config); // Trigger event OnConfigChanged(config, ConfigChangeType.VariableUpdated, variableName); return config; } public async Task AddVariableAsync(Guid configId, ConfigVariable variable) { var config = await GetConfigByIdAsync(configId) ?? throw new KeyNotFoundException($"Config with ID '{configId}' not found"); // Check if variable name already exists if (config.Variables.Any(v => v.Name.Equals(variable.Name, StringComparison.OrdinalIgnoreCase))) { throw new InvalidOperationException($"Variable '{variable.Name}' already exists in config"); } config.Variables.Add(variable); config.UpdatedAt = DateTime.UtcNow; // Validate var validationResult = ConfigValidator.ValidateConfig(config); if (!validationResult.IsValid) { throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}"); } // Save config file (includes metadata) await SaveConfigFileAsync(config); // Trigger event OnConfigChanged(config, ConfigChangeType.VariableAdded, variable.Name); return config; } public async Task RemoveVariableAsync(Guid configId, string variableName) { var config = await GetConfigByIdAsync(configId) ?? throw new KeyNotFoundException($"Config with ID '{configId}' not found"); var variable = config.Variables.FirstOrDefault(v => v.Name.Equals(variableName, StringComparison.OrdinalIgnoreCase)) ?? throw new KeyNotFoundException($"Variable '{variableName}' not found in config"); config.Variables.Remove(variable); config.UpdatedAt = DateTime.UtcNow; // Validate var validationResult = ConfigValidator.ValidateConfig(config); if (!validationResult.IsValid) { throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}"); } // Save config file (includes metadata) await SaveConfigFileAsync(config); // Trigger event OnConfigChanged(config, ConfigChangeType.VariableRemoved, variableName); return config; } // ========================================== // PRIVATE HELPER METHODS // ========================================== /// /// Load config file từ StorageManager (format mới với metadata) /// private async Task LoadConfigFileAsync(string configType) { try { // objectName should be {configType}.config so StorageManager finds {configType}.config.json var objectName = $"{configType}.config"; var exists = await _storageManager.ExistsAsync(ConfigPath, objectName, CancellationToken.None); if (!exists) { return null; } // Get file stream from StorageManager (works with both local and remote storage) using var stream = await _storageManager.GetFileAsync(ConfigPath, objectName, CancellationToken.None); // Parse config file directly from stream (format mới với metadata) return JsonConfigParser.ParseConfigFile(stream); } catch (JsonException) { // Config file corrupted/invalid format - treat as not found return null; } } /// /// Save config file vào StorageManager (format mới với metadata) /// private async Task SaveConfigFileAsync(ConfigFile config) { // Serialize với format mới (metadata + variables) var json = JsonConfigParser.SerializeConfigFile(config); var bytes = Encoding.UTF8.GetBytes(json); // objectName should be {configType}.config so StorageManager adds .json to make {configType}.config.json var objectName = $"{config.ConfigType}.config"; using var stream = new MemoryStream(bytes); await _storageManager.UploadAsync( ConfigPath, objectName, stream, stream.Length, "application/json", CancellationToken.None ); } }