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);
}
}