using Microsoft.EntityFrameworkCore; public class GatewayRegistryService : IGatewayRegistryService { private readonly AppDbContext _db; public GatewayRegistryService(AppDbContext db) => _db = db; public async Task RegisterAsync(Guid siteId, RegisterGatewayRequest req) { var siteExists = await _db.ClinicalSites.AnyAsync(s => s.Id == siteId); if (!siteExists) throw new NotFoundException("Clinical site not found.", "SITE_NOT_FOUND"); var duplicate = await _db.WardGateways.AnyAsync(g => g.SiteId == siteId && g.GatewayCode == req.GatewayCode); if (duplicate) throw new ConflictException( $"Gateway code '{req.GatewayCode}' already exists for this site.", "GATEWAY_CODE_CONFLICT"); var gateway = new WardGateway(siteId, req.GatewayCode, req.Department); _db.WardGateways.Add(gateway); await _db.SaveChangesAsync(); return gateway; } public async Task GetByIdAsync(Guid id) { var gateway = await _db.WardGateways .AsNoTracking() .Include(g => g.Site) .FirstOrDefaultAsync(g => g.Id == id); if (gateway is null) throw new NotFoundException("Ward gateway not found.", "GATEWAY_NOT_FOUND"); return gateway; } public async Task> ListBySiteAsync(Guid siteId, string? department) { var siteExists = await _db.ClinicalSites.AnyAsync(s => s.Id == siteId); if (!siteExists) throw new NotFoundException("Clinical site not found.", "SITE_NOT_FOUND"); var query = _db.WardGateways.AsNoTracking().Where(g => g.SiteId == siteId); if (!string.IsNullOrWhiteSpace(department)) query = query.Where(g => g.Department == department); return await query.OrderBy(g => g.GatewayCode).ToListAsync(); } public async Task RecordHeartbeatAsync( Guid gatewayId, GatewayHeartbeatRequest req, string? authenticatedGatewayId) { if (authenticatedGatewayId is not null && Guid.TryParse(authenticatedGatewayId, out var authId) && authId != gatewayId) throw new ValidationException( "Gateway id in URL does not match X-Gateway-Id header.", "GATEWAY_ID_MISMATCH"); var gateway = await _db.WardGateways.FindAsync(gatewayId); if (gateway is null) throw new NotFoundException("Ward gateway not found.", "GATEWAY_NOT_FOUND"); var status = GatewayStatusExtensions.FromDbString(req.Status.ToUpperInvariant()); gateway.RecordHeartbeat(status, req.BufferDepth, req.ReportedAtUtc); await _db.SaveChangesAsync(); return gateway; } public async Task MarkOfflineAsync(Guid gatewayId) { var gateway = await _db.WardGateways.FindAsync(gatewayId); if (gateway is null) throw new NotFoundException("Ward gateway not found.", "GATEWAY_NOT_FOUND"); gateway.MarkOffline(); await _db.SaveChangesAsync(); } }