using System.Security.Cryptography; using System.Text; using Microsoft.Extensions.Options; public class PhiAccessLogService : IPhiAccessLogService { private readonly AppDbContext _db; private readonly ICurrentUserService _currentUser; private readonly IHttpContextAccessor _http; private readonly PhiEncryptionOptions _options; public PhiAccessLogService( AppDbContext db, ICurrentUserService currentUser, IHttpContextAccessor http, IOptions options) { _db = db; _currentUser = currentUser; _http = http; _options = options.Value; } public async Task LogViewAsync(Guid patientId, string resourcePath) => await WriteAsync(PhiAccessType.View, patientId, resourcePath); public async Task LogCreateAsync(Guid patientId, string resourcePath) => await WriteAsync(PhiAccessType.Create, patientId, resourcePath); public async Task LogUpdateAsync(Guid patientId, string resourcePath) => await WriteAsync(PhiAccessType.Update, patientId, resourcePath); public async Task LogListAsync(string resourcePath, int resultCount, string? searchQuery = null) { if (!_options.LogListAccess) return; var accessType = string.IsNullOrWhiteSpace(searchQuery) ? PhiAccessType.List : PhiAccessType.Search; await WriteAsync(accessType, null, resourcePath, resultCount, searchQuery); } private async Task WriteAsync( PhiAccessType accessType, Guid? patientId, string resourcePath, int? resultCount = null, string? searchQuery = null) { if (!_currentUser.IsAuthenticated || _currentUser.UserId is null) return; // machine/integration paths may skip — Phase 31 Integration role should still auth _db.PhiAccessLogs.Add(new PhiAccessLog { Id = Guid.NewGuid(), AccessType = accessType, PatientId = patientId, UserId = _currentUser.UserId.Value, UserDisplayName = _currentUser.DisplayName ?? _currentUser.Username ?? "Unknown", ResourcePath = resourcePath, SearchQueryHash = searchQuery is null ? null : HashQuery(searchQuery), ResultCount = resultCount, IpAddress = _currentUser.IpAddress, CorrelationId = _http.HttpContext?.Items["CorrelationId"]?.ToString(), AccessedAt = DateTimeOffset.UtcNow }); await _db.SaveChangesAsync(); } private static string HashQuery(string query) { var hash = SHA256.HashData(Encoding.UTF8.GetBytes(query.Trim().ToLowerInvariant())); return Convert.ToHexString(hash).ToLowerInvariant(); } }