using System.Collections.Concurrent; namespace Campaign_Tracker.Server.Municipalities; public sealed class InMemoryMunicipalityAddressRepository : IMunicipalityAddressRepository { private readonly ConcurrentDictionary _addresses = new(StringComparer.OrdinalIgnoreCase); private readonly object _lock = new(); private readonly TimeProvider _timeProvider; public InMemoryMunicipalityAddressRepository(TimeProvider timeProvider) { _timeProvider = timeProvider; } public Task> GetByProfileIdAsync( string profileId, CancellationToken cancellationToken = default) { var result = _addresses.Values .Where(a => a.ProfileId == profileId && !a.IsDeleted) .OrderByDescending(a => a.EffectiveDate) .ToArray(); return Task.FromResult>(result); } public Task GetByIdAsync( string addressId, CancellationToken cancellationToken = default) { _addresses.TryGetValue(addressId, out var address); return Task.FromResult(address is { IsDeleted: false } ? address : null); } public Task AddAsync( string profileId, string addressType, string street, string city, string state, string zipCode, DateTimeOffset effectiveDate, string actorIdentity, CancellationToken cancellationToken = default) { var now = _timeProvider.GetUtcNow(); lock (_lock) { // Per-type history: mark existing current address of same type as not-current foreach (var existing in _addresses.Values .Where(a => a.ProfileId == profileId && a.AddressType == addressType && a.IsCurrent && !a.IsDeleted)) { _addresses[existing.AddressId] = existing with { IsCurrent = false, UpdatedAt = now, UpdatedBy = actorIdentity }; } var address = new MunicipalityAddress( AddressId: Guid.NewGuid().ToString("N"), ProfileId: profileId, AddressType: addressType, Street: street, City: city, State: state, ZipCode: zipCode, EffectiveDate: effectiveDate, IsCurrent: true, IsDeleted: false, CreatedAt: now, CreatedBy: actorIdentity, UpdatedAt: now, UpdatedBy: actorIdentity); _addresses[address.AddressId] = address; return Task.FromResult(MunicipalityAddressSaveResult.Success(address)); } } public Task UpdateAsync( string addressId, string addressType, string street, string city, string state, string zipCode, DateTimeOffset effectiveDate, string actorIdentity, CancellationToken cancellationToken = default) { var now = _timeProvider.GetUtcNow(); lock (_lock) { if (!_addresses.TryGetValue(addressId, out var existing) || existing.IsDeleted) return Task.FromResult(MunicipalityAddressSaveResult.NotFound(addressId)); // Preserve the old record in history by marking it not-current _addresses[addressId] = existing with { IsCurrent = false, UpdatedAt = now, UpdatedBy = actorIdentity }; // Mark any other current addresses of the same type as not-current foreach (var other in _addresses.Values .Where(a => a.ProfileId == existing.ProfileId && a.AddressType == addressType && a.IsCurrent && a.AddressId != addressId && !a.IsDeleted)) { _addresses[other.AddressId] = other with { IsCurrent = false, UpdatedAt = now, UpdatedBy = actorIdentity }; } // Insert a new current record (history-preserving update) var updated = new MunicipalityAddress( AddressId: Guid.NewGuid().ToString("N"), ProfileId: existing.ProfileId, AddressType: addressType, Street: street, City: city, State: state, ZipCode: zipCode, EffectiveDate: effectiveDate, IsCurrent: true, IsDeleted: false, CreatedAt: now, CreatedBy: actorIdentity, UpdatedAt: now, UpdatedBy: actorIdentity); _addresses[updated.AddressId] = updated; return Task.FromResult(MunicipalityAddressSaveResult.Success(updated)); } } public Task SoftDeleteAsync( string addressId, string actorIdentity, CancellationToken cancellationToken = default) { var now = _timeProvider.GetUtcNow(); lock (_lock) { if (!_addresses.TryGetValue(addressId, out var existing) || existing.IsDeleted) return Task.FromResult(MunicipalityAddressSaveResult.NotFound(addressId)); _addresses[addressId] = existing with { IsDeleted = true, IsCurrent = false, UpdatedAt = now, UpdatedBy = actorIdentity }; return Task.FromResult(MunicipalityAddressSaveResult.Success(_addresses[addressId])); } } }