using CredentialManager.Models; using Microsoft.Extensions.Logging; namespace Data_Coupler.Services; /// /// Servizio per l'esecuzione automatica dei trasferimenti dati basati sui profili /// public interface IDataTransferService { Task ExecuteProfileAsync(DataCouplerProfile profile, string? sourceDatabaseOverride = null, string? destinationDatabaseOverride = null, bool enableDeletionSync = false); } public class DataTransferResult { public bool IsSuccess { get; set; } public int RecordsProcessed { get; set; } public string? ErrorMessage { get; set; } public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public List ErrorDetails { get; set; } = new(); public Dictionary AdditionalInfo { get; set; } = new(); public TimeSpan Duration => EndTime - StartTime; } public class DataTransferService : IDataTransferService { private readonly IScheduledProfileExecutionService _scheduledExecutionService; private readonly ILogger _logger; public DataTransferService( IScheduledProfileExecutionService scheduledExecutionService, ILogger logger) { _scheduledExecutionService = scheduledExecutionService ?? throw new ArgumentNullException(nameof(scheduledExecutionService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task ExecuteProfileAsync(DataCouplerProfile profile, string? sourceDatabaseOverride = null, string? destinationDatabaseOverride = null, bool enableDeletionSync = false) { var result = new DataTransferResult { StartTime = DateTime.Now // Usa l'ora locale per coerenza con le schedulazioni }; try { _logger.LogInformation("Iniziando esecuzione profilo {ProfileName} (ID: {ProfileId})", profile.Name, profile.Id); // Validazione del profilo var validationResult = ValidateProfile(profile); if (!validationResult.IsValid) { result.IsSuccess = false; result.ErrorMessage = validationResult.ErrorMessage; result.EndTime = DateTime.Now; // Usa l'ora locale per coerenza return result; } // Applica override del database se specificati var profileToExecute = await ApplyDatabaseOverrides(profile, sourceDatabaseOverride, destinationDatabaseOverride); // Utilizza il servizio esistente per l'esecuzione var executionResult = await _scheduledExecutionService.ExecuteProfileAsync(profileToExecute.Id, enableDeletionSync); result.IsSuccess = executionResult.Success; result.RecordsProcessed = executionResult.RecordsProcessed; result.ErrorMessage = executionResult.Success ? null : executionResult.Message; result.EndTime = DateTime.Now; // Usa l'ora locale per coerenza if (executionResult.Success) { result.AdditionalInfo["ExecutionDuration"] = executionResult.Duration.ToString(); _logger.LogInformation("Profilo {ProfileName} eseguito con successo. " + "Record processati: {RecordsProcessed}, Durata: {Duration}ms", profile.Name, result.RecordsProcessed, result.Duration.TotalMilliseconds); } else { result.ErrorDetails.Add(executionResult.Message); _logger.LogError("Errore nell'esecuzione del profilo {ProfileName}: {ErrorMessage}", profile.Name, executionResult.Message); } return result; } catch (Exception ex) { _logger.LogError(ex, "Errore durante l'esecuzione del profilo {ProfileName} (ID: {ProfileId})", profile.Name, profile.Id); result.IsSuccess = false; result.ErrorMessage = ex.Message; result.ErrorDetails.Add($"Exception: {ex.GetType().Name} - {ex.Message}"); if (ex.InnerException != null) { result.ErrorDetails.Add($"Inner Exception: {ex.InnerException.Message}"); } result.EndTime = DateTime.Now; // Usa l'ora locale per coerenza return result; } } private async Task ApplyDatabaseOverrides(DataCouplerProfile originalProfile, string? sourceDatabaseOverride, string? destinationDatabaseOverride) { // Se non ci sono override, restituisce il profilo originale if (string.IsNullOrEmpty(sourceDatabaseOverride) && string.IsNullOrEmpty(destinationDatabaseOverride)) { return originalProfile; } // Crea una copia del profilo con gli override applicati // In un'implementazione reale, potresti voler creare una copia più sofisticata // o utilizzare un metodo di clonazione appropriato var profileCopy = new DataCouplerProfile { Id = originalProfile.Id, Name = originalProfile.Name, Description = originalProfile.Description, SourceType = originalProfile.SourceType, SourceCredentialId = originalProfile.SourceCredentialId, SourceDatabaseName = originalProfile.SourceDatabaseName, SourceTable = originalProfile.SourceTable, SourceSchema = originalProfile.SourceSchema, SourceCustomQuery = originalProfile.SourceCustomQuery, SourceFilePath = originalProfile.SourceFilePath, DestinationType = originalProfile.DestinationType, DestinationCredentialId = originalProfile.DestinationCredentialId, DestinationTable = originalProfile.DestinationTable, DestinationSchema = originalProfile.DestinationSchema, DestinationEndpoint = originalProfile.DestinationEndpoint, FieldMappingJson = originalProfile.FieldMappingJson, SourceKeyField = originalProfile.SourceKeyField, UseRecordAssociations = originalProfile.UseRecordAssociations, IsActive = originalProfile.IsActive, CreatedAt = originalProfile.CreatedAt, LastUsedAt = originalProfile.LastUsedAt, CreatedBy = originalProfile.CreatedBy }; // TODO: Implementare l'applicazione degli override del database // Questo richiederebbe di modificare temporaneamente la stringa di connessione // delle credenziali per puntare al database specificato _logger.LogInformation("Applicazione override database - Source: {SourceDB}, Destination: {DestDB}", sourceDatabaseOverride ?? "none", destinationDatabaseOverride ?? "none"); return await Task.FromResult(profileCopy); } private (bool IsValid, string? ErrorMessage) ValidateProfile(DataCouplerProfile profile) { if (profile == null) return (false, "Profilo non specificato"); if (string.IsNullOrEmpty(profile.Name)) return (false, "Nome profilo non specificato"); if (string.IsNullOrEmpty(profile.SourceType)) return (false, "Tipo sorgente non specificato"); if (string.IsNullOrEmpty(profile.DestinationType)) return (false, "Tipo destinazione non specificato"); // Per le sorgenti file, la credenziale non è richiesta if (profile.SourceType != "file" && !profile.SourceCredentialId.HasValue) return (false, "Credenziale sorgente non specificata"); if (!profile.DestinationCredentialId.HasValue) return (false, "Credenziale destinazione non specificata"); // Validazioni specifiche per tipo sorgente if (profile.SourceType == "database") { if (string.IsNullOrEmpty(profile.SourceTable) && string.IsNullOrEmpty(profile.SourceCustomQuery)) return (false, "Tabella sorgente o query personalizzata deve essere specificata"); } else if (profile.SourceType == "file") { if (string.IsNullOrEmpty(profile.SourceFilePath)) return (false, "Percorso file sorgente non specificato"); } // Validazioni specifiche per tipo destinazione if (profile.DestinationType == "database") { if (string.IsNullOrEmpty(profile.DestinationTable)) return (false, "Tabella destinazione non specificata"); } else if (profile.DestinationType == "rest") { if (string.IsNullOrEmpty(profile.DestinationEndpoint)) return (false, "Endpoint REST destinazione non specificato"); } return (true, null); } }