using System.Collections.Concurrent;
using NeonSprawl.Server.Game.Rewards;
namespace NeonSprawl.Server.Game.Factions;
/// Thread-safe in-memory append-only reputation delta audit log (NEO-135).
public sealed class InMemoryReputationDeltaStore : IReputationDeltaStore
{
private readonly ConcurrentDictionary rowsById = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary idLocks = new(StringComparer.Ordinal);
///
public bool TryAppend(ReputationDeltaRow row)
{
if (!IsValidRow(row))
{
return false;
}
var normalized = row with
{
Id = row.Id.Trim(),
PlayerId = FactionStandingIds.NormalizePlayerId(row.PlayerId),
FactionId = FactionStandingIds.NormalizeFactionId(row.FactionId),
SourceKind = row.SourceKind.Trim(),
SourceId = row.SourceId.Trim(),
};
lock (idLocks.GetOrAdd(normalized.Id, _ => new object()))
{
if (rowsById.ContainsKey(normalized.Id))
{
return false;
}
rowsById[normalized.Id] = normalized;
return true;
}
}
///
public IReadOnlyList GetDeltasForPlayer(string playerId, int? limit = null)
{
var player = FactionStandingIds.NormalizePlayerId(playerId);
if (player.Length == 0)
{
return [];
}
IEnumerable query = rowsById.Values
.Where(r => string.Equals(r.PlayerId, player, StringComparison.Ordinal))
.OrderBy(static r => r.RecordedAt)
.ThenBy(static r => r.Id, StringComparer.Ordinal);
if (limit is > 0)
{
query = query.Take(limit.Value);
}
return query.ToArray();
}
///
public bool TryClearQuestCompletionAudit(string playerId, string questId)
{
var player = FactionStandingIds.NormalizePlayerId(playerId);
var sourceId = RewardDeliveryIds.NormalizeQuestId(questId);
if (player.Length == 0 || sourceId.Length == 0)
{
return false;
}
var idsToRemove = rowsById.Values
.Where(row =>
string.Equals(row.PlayerId, player, StringComparison.Ordinal) &&
string.Equals(row.SourceKind, ReputationDeltaSourceKinds.QuestCompletion, StringComparison.Ordinal) &&
string.Equals(row.SourceId, sourceId, StringComparison.Ordinal))
.Select(static row => row.Id)
.ToArray();
foreach (var id in idsToRemove)
{
rowsById.TryRemove(id, out _);
idLocks.TryRemove(id, out _);
}
return true;
}
private static bool IsValidRow(ReputationDeltaRow row) =>
row.Id.Trim().Length > 0 &&
FactionStandingIds.NormalizePlayerId(row.PlayerId).Length > 0 &&
FactionStandingIds.NormalizeFactionId(row.FactionId).Length > 0 &&
row.SourceKind.Trim().Length > 0 &&
row.SourceId.Trim().Length > 0;
}