using CredentialManager.Data; using CredentialManager.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace CredentialManager.Services; /// /// Servizio per la gestione delle associazioni basate sui valori delle chiavi /// public class KeyAssociationService : IKeyAssociationService { private readonly CredentialDbContext _context; private readonly ILogger _logger; public KeyAssociationService( CredentialDbContext context, ILogger logger) { _context = context; _logger = logger; } public async Task SaveAssociationAsync(KeyAssociation association) { try { _logger.LogInformation("DEBUG: Tentativo salvataggio associazione - KeyValue: '{KeyValue}', DestinationEntity: '{DestinationEntity}', DestinationId: '{DestinationId}', RestCredentialName: '{RestCredentialName}'", association.KeyValue, association.DestinationEntity, association.DestinationId, association.RestCredentialName); // Controlla se esiste già un'associazione per questo valore chiave e destinazione var existing = await _context.KeyAssociations .FirstOrDefaultAsync(ka => ka.KeyValue == association.KeyValue && ka.DestinationEntity == association.DestinationEntity && ka.RestCredentialName == association.RestCredentialName && ka.IsActive); _logger.LogInformation("DEBUG: Controllo associazione esistente: {Found}. ID: {Id}", existing != null, existing?.Id); if (existing != null) { // Aggiorna l'associazione esistente existing.DestinationId = association.DestinationId; existing.SourceKeyField = association.SourceKeyField; existing.DestinationKeyField = association.DestinationKeyField; existing.UpdatedAt = DateTime.UtcNow; existing.LastVerifiedAt = DateTime.UtcNow; existing.AdditionalInfo = association.AdditionalInfo; // Aggiorna le informazioni sulle sorgenti UpdateSourcesInfo(existing, association); _context.KeyAssociations.Update(existing); await _context.SaveChangesAsync(); _logger.LogInformation("Associazione aggiornata: KeyValue={KeyValue} -> {DestinationEntity}/{DestinationId}", association.KeyValue, association.DestinationEntity, association.DestinationId); return existing.Id; } else { // Crea nuova associazione association.CreatedAt = DateTime.UtcNow; association.LastVerifiedAt = DateTime.UtcNow; _context.KeyAssociations.Add(association); await _context.SaveChangesAsync(); _logger.LogInformation("Nuova associazione creata: KeyValue={KeyValue} -> {DestinationEntity}/{DestinationId}", association.KeyValue, association.DestinationEntity, association.DestinationId); return association.Id; } } catch (Exception ex) { _logger.LogError(ex, "Errore nel salvare l'associazione: KeyValue={KeyValue} -> {DestinationEntity}", association.KeyValue, association.DestinationEntity); throw; } } public async Task FindAssociationByKeyValueAsync(string keyValue, string destinationEntity, string restCredentialName) { try { _logger.LogInformation("DEBUG: Ricerca associazione con parametri - KeyValue: '{KeyValue}', DestinationEntity: '{DestinationEntity}', RestCredentialName: '{RestCredentialName}'", keyValue, destinationEntity, restCredentialName); var result = await _context.KeyAssociations .FirstOrDefaultAsync(ka => ka.KeyValue == keyValue && ka.DestinationEntity == destinationEntity && ka.RestCredentialName == restCredentialName && ka.IsActive); _logger.LogInformation("DEBUG: Risultato ricerca associazione: {Found}. ID: {Id}, DestinationId: '{DestinationId}'", result != null, result?.Id, result?.DestinationId); return result; } catch (Exception ex) { _logger.LogError(ex, "Errore nella ricerca dell'associazione: KeyValue={KeyValue} -> {DestinationEntity}", keyValue, destinationEntity); throw; } } public async Task FindAssociationByKeyValueAsync(string keyValue) { try { return await _context.KeyAssociations .Where(ka => ka.KeyValue == keyValue && ka.IsActive) .OrderByDescending(ka => ka.UpdatedAt ?? ka.CreatedAt) .FirstOrDefaultAsync(); } catch (Exception ex) { _logger.LogError(ex, "Errore nella ricerca dell'associazione per KeyValue={KeyValue}", keyValue); throw; } } public async Task> GetAssociationsByDestinationAsync(string destinationEntity, string restCredentialName) { try { return await _context.KeyAssociations .Where(ka => ka.DestinationEntity == destinationEntity && ka.RestCredentialName == restCredentialName && ka.IsActive) .OrderByDescending(ka => ka.CreatedAt) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Errore nel recupero delle associazioni per destinazione: {DestinationEntity} ({RestCredentialName})", destinationEntity, restCredentialName); throw; } } public async Task> GetAllActiveAssociationsAsync() { try { return await _context.KeyAssociations .Where(ka => ka.IsActive) .OrderByDescending(ka => ka.CreatedAt) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Errore nel recupero di tutte le associazioni attive"); throw; } } public async Task> GetAllAssociationsAsync() { try { return await _context.KeyAssociations .OrderByDescending(ka => ka.CreatedAt) .ToListAsync(); } catch (Exception ex) { _logger.LogError(ex, "Errore nel recupero di tutte le associazioni"); throw; } } public async Task UpdateAssociationAsync(KeyAssociation association) { try { var existing = await _context.KeyAssociations.FindAsync(association.Id); if (existing == null) { _logger.LogWarning("Associazione con ID {Id} non trovata per l'aggiornamento", association.Id); return false; } existing.KeyValue = association.KeyValue; existing.SourceKeyField = association.SourceKeyField; existing.DestinationKeyField = association.DestinationKeyField; existing.DestinationId = association.DestinationId; existing.RestCredentialName = association.RestCredentialName; existing.UpdatedAt = DateTime.UtcNow; existing.AdditionalInfo = association.AdditionalInfo; existing.SourcesInfo = association.SourcesInfo; existing.IsActive = association.IsActive; _context.KeyAssociations.Update(existing); await _context.SaveChangesAsync(); _logger.LogInformation("Associazione aggiornata: ID {Id}", association.Id); return true; } catch (Exception ex) { _logger.LogError(ex, "Errore nell'aggiornamento dell'associazione: ID {Id}", association.Id); throw; } } public async Task DeactivateAssociationAsync(int id) { try { var association = await _context.KeyAssociations.FindAsync(id); if (association == null) { _logger.LogWarning("Associazione con ID {Id} non trovata per la disattivazione", id); return false; } association.IsActive = false; association.UpdatedAt = DateTime.UtcNow; _context.KeyAssociations.Update(association); await _context.SaveChangesAsync(); _logger.LogInformation("Associazione disattivata: ID {Id}", id); return true; } catch (Exception ex) { _logger.LogError(ex, "Errore nella disattivazione dell'associazione: ID {Id}", id); throw; } } public async Task DeleteAssociationAsync(int id) { try { var association = await _context.KeyAssociations.FindAsync(id); if (association == null) { _logger.LogWarning("Associazione con ID {Id} non trovata per l'eliminazione", id); return false; } _context.KeyAssociations.Remove(association); await _context.SaveChangesAsync(); _logger.LogInformation("Associazione eliminata: ID {Id}", id); return true; } catch (Exception ex) { _logger.LogError(ex, "Errore nell'eliminazione dell'associazione: ID {Id}", id); throw; } } public async Task CleanupOldAssociationsAsync(TimeSpan olderThan) { try { var cutoffDate = DateTime.UtcNow - olderThan; var oldAssociations = await _context.KeyAssociations .Where(ka => ka.CreatedAt < cutoffDate && !ka.IsActive) .ToListAsync(); if (oldAssociations.Any()) { _context.KeyAssociations.RemoveRange(oldAssociations); await _context.SaveChangesAsync(); _logger.LogInformation("Pulite {Count} associazioni obsolete più vecchie di {Cutoff}", oldAssociations.Count, cutoffDate); } return oldAssociations.Count; } catch (Exception ex) { _logger.LogError(ex, "Errore nella pulizia delle associazioni obsolete"); throw; } } public async Task ClearAssociationsAsync(string destinationEntity, string restCredentialName) { try { var associationsToDelete = await _context.KeyAssociations .Where(ka => ka.DestinationEntity == destinationEntity && ka.RestCredentialName == restCredentialName) .ToListAsync(); if (associationsToDelete.Any()) { _context.KeyAssociations.RemoveRange(associationsToDelete); await _context.SaveChangesAsync(); _logger.LogInformation("Eliminate {Count} associazioni per {DestinationEntity}", associationsToDelete.Count, destinationEntity); } return associationsToDelete.Count; } catch (Exception ex) { _logger.LogError(ex, "Errore nella cancellazione delle associazioni per {DestinationEntity}", destinationEntity); throw; } } public async Task ClearAllAssociationsAsync() { try { var allAssociations = await _context.KeyAssociations.ToListAsync(); var count = allAssociations.Count; if (allAssociations.Any()) { _context.KeyAssociations.RemoveRange(allAssociations); await _context.SaveChangesAsync(); _logger.LogWarning("Eliminate TUTTE le {Count} associazioni dal sistema", count); } return count; } catch (Exception ex) { _logger.LogError(ex, "Errore nella cancellazione di tutte le associazioni"); throw; } } public async Task ValidateDestinationIdAsync(string destinationId, string destinationEntity, string restCredentialName) { // Questa implementazione base restituisce sempre true // Dovrebbe essere estesa per verificare effettivamente l'esistenza nel sistema REST try { // TODO: Implementare la logica di validazione effettiva con il servizio REST // Per ora assumiamo che l'ID sia valido _logger.LogDebug("Validazione ID destinazione {DestinationId} per entità {DestinationEntity} - Non implementata", destinationId, destinationEntity); return await Task.FromResult(true); } catch (Exception ex) { _logger.LogError(ex, "Errore nella validazione dell'ID destinazione {DestinationId}", destinationId); return false; } } public async Task> GetInvalidAssociationsAsync(string destinationEntity, string restCredentialName) { try { var associations = await _context.KeyAssociations .Where(ka => ka.DestinationEntity == destinationEntity && ka.RestCredentialName == restCredentialName && ka.IsActive) .ToListAsync(); var invalidAssociations = new List(); // Verifica ogni associazione foreach (var association in associations) { var isValid = await ValidateDestinationIdAsync(association.DestinationId, destinationEntity, restCredentialName); if (!isValid) { invalidAssociations.Add(association); } } _logger.LogInformation("Trovate {Invalid}/{Total} associazioni non valide per {DestinationEntity}", invalidAssociations.Count, associations.Count, destinationEntity); return invalidAssociations; } catch (Exception ex) { _logger.LogError(ex, "Errore nel recupero delle associazioni non valide per {DestinationEntity}", destinationEntity); throw; } } public async Task CleanupInvalidAssociationsAsync(string destinationEntity, string restCredentialName) { try { var invalidAssociations = await GetInvalidAssociationsAsync(destinationEntity, restCredentialName); if (invalidAssociations.Any()) { _context.KeyAssociations.RemoveRange(invalidAssociations); await _context.SaveChangesAsync(); _logger.LogWarning("Eliminate {Count} associazioni non valide per {DestinationEntity}", invalidAssociations.Count, destinationEntity); } return invalidAssociations.Count; } catch (Exception ex) { _logger.LogError(ex, "Errore nella pulizia delle associazioni non valide per {DestinationEntity}", destinationEntity); throw; } } public async Task UpdateLastVerifiedAsync(int id) { try { var association = await _context.KeyAssociations.FindAsync(id); if (association == null) { _logger.LogWarning("Associazione con ID {Id} non trovata per l'aggiornamento della verifica", id); return false; } association.LastVerifiedAt = DateTime.UtcNow; _context.KeyAssociations.Update(association); await _context.SaveChangesAsync(); return true; } catch (Exception ex) { _logger.LogError(ex, "Errore nell'aggiornamento della verifica per associazione: ID {Id}", id); throw; } } public async Task GetStatisticsAsync() { try { var allAssociations = await _context.KeyAssociations.ToListAsync(); var stats = new AssociationStatistics { TotalAssociations = allAssociations.Count, ActiveAssociations = allAssociations.Count(a => a.IsActive), InactiveAssociations = allAssociations.Count(a => !a.IsActive), UniqueKeyValues = allAssociations.Select(a => a.KeyValue).Distinct().Count(), UniqueDestinationEntities = allAssociations.Select(a => a.DestinationEntity).Distinct().Count(), OldestAssociation = allAssociations.Any() ? allAssociations.Min(a => a.CreatedAt) : null, NewestAssociation = allAssociations.Any() ? allAssociations.Max(a => a.CreatedAt) : null, AssociationsByEntity = allAssociations .GroupBy(a => a.DestinationEntity) .ToDictionary(g => g.Key, g => g.Count()) }; return stats; } catch (Exception ex) { _logger.LogError(ex, "Errore nel calcolo delle statistiche delle associazioni"); throw; } } private void UpdateSourcesInfo(KeyAssociation existing, KeyAssociation newAssociation) { try { var sourcesInfo = existing.SourcesInfo ?? ""; var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm"); var newSourceInfo = $"{timestamp}: {newAssociation.SourceKeyField}"; if (!sourcesInfo.Contains(newSourceInfo)) { existing.SourcesInfo = string.IsNullOrEmpty(sourcesInfo) ? newSourceInfo : $"{sourcesInfo}; {newSourceInfo}"; } } catch (Exception ex) { _logger.LogWarning(ex, "Errore nell'aggiornamento delle informazioni sulle sorgenti"); } } }