using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services; using ZB.MOM.WW.ScadaBridge.Commons.Types; namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Services; /// /// Site and data connection management. /// - Site CRUD (name, identifier, description) /// - Data connection CRUD (name, protocol, config) — each connection belongs to exactly one site /// - Audit logging /// public class SiteService { private readonly ISiteRepository _repository; private readonly IAuditService _auditService; /// Initializes the service with its repository and audit collaborators. /// Repository for site and data connection persistence. /// Audit service for logging CRUD operations. public SiteService(ISiteRepository repository, IAuditService auditService) { _repository = repository ?? throw new ArgumentNullException(nameof(repository)); _auditService = auditService ?? throw new ArgumentNullException(nameof(auditService)); } // --- Site CRUD --- /// Creates a new site with the given name and identifier, rejecting duplicates. /// Display name of the site. /// Unique machine-readable site identifier. /// Optional description. /// Authenticated username for audit logging. /// Cancellation token. /// A successful result containing the new , or a failure result with a message. public async Task> CreateSiteAsync( string name, string siteIdentifier, string? description, string user, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(name)) return Result.Failure("Site name is required."); if (string.IsNullOrWhiteSpace(siteIdentifier)) return Result.Failure("Site identifier is required."); var existing = await _repository.GetSiteByIdentifierAsync(siteIdentifier, cancellationToken); if (existing != null) return Result.Failure($"Site with identifier '{siteIdentifier}' already exists."); var site = new Site(name, siteIdentifier) { Description = description }; await _repository.AddSiteAsync(site, cancellationToken); await _repository.SaveChangesAsync(cancellationToken); await _auditService.LogAsync(user, "Create", "Site", site.Id.ToString(), name, site, cancellationToken); return Result.Success(site); } /// Updates the name and description of an existing site. /// Primary key of the site to update. /// New display name. /// New optional description. /// Authenticated username for audit logging. /// Cancellation token. /// A successful result containing the updated , or a failure result if not found. public async Task> UpdateSiteAsync( int siteId, string name, string? description, string user, CancellationToken cancellationToken = default) { var site = await _repository.GetSiteByIdAsync(siteId, cancellationToken); if (site == null) return Result.Failure($"Site with ID {siteId} not found."); site.Name = name; site.Description = description; await _repository.UpdateSiteAsync(site, cancellationToken); await _repository.SaveChangesAsync(cancellationToken); await _auditService.LogAsync(user, "Update", "Site", site.Id.ToString(), name, site, cancellationToken); return Result.Success(site); } /// Deletes a site, rejecting the request if any instances are assigned to it. /// Primary key of the site to delete. /// Authenticated username for audit logging. /// Cancellation token. /// A successful result with true, or a failure result with a message if blocked. public async Task> DeleteSiteAsync(int siteId, string user, CancellationToken cancellationToken = default) { var site = await _repository.GetSiteByIdAsync(siteId, cancellationToken); if (site == null) return Result.Failure($"Site with ID {siteId} not found."); // Check for instances assigned to this site var instances = await _repository.GetInstancesBySiteIdAsync(siteId, cancellationToken); if (instances.Count > 0) { var names = string.Join(", ", instances.Select(i => i.UniqueName).Take(5)); return Result.Failure( $"Cannot delete site '{site.Name}': {instances.Count} instance(s) are assigned to it ({names}{(instances.Count > 5 ? "..." : "")})."); } await _repository.DeleteSiteAsync(siteId, cancellationToken); await _repository.SaveChangesAsync(cancellationToken); await _auditService.LogAsync(user, "Delete", "Site", siteId.ToString(), site.Name, null, cancellationToken); return Result.Success(true); } /// Returns the site with the given primary key, or null if not found. /// Primary key of the site. /// Cancellation token. /// A task that resolves to the matching , or null if not found. public async Task GetSiteByIdAsync(int siteId, CancellationToken cancellationToken = default) => await _repository.GetSiteByIdAsync(siteId, cancellationToken); /// Returns all sites. /// Cancellation token. /// A task that resolves to the complete list of all sites. public async Task> GetAllSitesAsync(CancellationToken cancellationToken = default) => await _repository.GetAllSitesAsync(cancellationToken); // --- Data Connection CRUD --- // Create/update of data connections deliberately live only in the Management // Service, which audits the secret-scrubbed public shape. The SiteService versions // (no production callers) audited the raw entity — Primary/BackupConfiguration can // embed protocol credentials — and were removed (review 2026-10-21 08/C1). /// Deletes a data connection by its primary key. /// Primary key of the data connection to delete. /// Authenticated username for audit logging. /// Cancellation token. /// A successful result with true, or a failure result if not found. public async Task> DeleteDataConnectionAsync( int connectionId, string user, CancellationToken cancellationToken = default) { var connection = await _repository.GetDataConnectionByIdAsync(connectionId, cancellationToken); if (connection == null) return Result.Failure($"Data connection with ID {connectionId} not found."); await _repository.DeleteDataConnectionAsync(connectionId, cancellationToken); await _repository.SaveChangesAsync(cancellationToken); await _auditService.LogAsync(user, "Delete", "DataConnection", connectionId.ToString(), connection.Name, null, cancellationToken); return Result.Success(true); } }