using System.Text.Json;

namespace JobTrackerApi.Services;

public sealed record AccountDeletionTombstone(string SchemaVersion, Guid RequestId, string OwnerKey, DateTimeOffset CompletedAtUtc);

public sealed class AccountDeletionTombstoneStore(AppPaths paths)
{
    private const string SchemaVersion = "jobtracker.account-deletion-tombstone.v1";
    private readonly SemaphoreSlim _gate = new(1, 1);
    private string LedgerPath => Path.Combine(paths.AccountDeletionTombstonesRoot, "tombstones.jsonl");

    public async Task AppendAsync(Guid requestId, string ownerKey, DateTimeOffset completedAtUtc, CancellationToken cancellationToken)
    {
        await _gate.WaitAsync(cancellationToken);
        try
        {
            var existing = await ReadUnsafeAsync(cancellationToken);
            if (existing.Any(item => item.RequestId == requestId)) return;
            Directory.CreateDirectory(paths.AccountDeletionTombstonesRoot);
            var line = JsonSerializer.Serialize(new AccountDeletionTombstone(SchemaVersion, requestId, ownerKey, completedAtUtc));
            await File.AppendAllTextAsync(LedgerPath, line + Environment.NewLine, cancellationToken);
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task<IReadOnlyList<AccountDeletionTombstone>> ReadAsync(CancellationToken cancellationToken)
    {
        await _gate.WaitAsync(cancellationToken);
        try { return await ReadUnsafeAsync(cancellationToken); }
        finally { _gate.Release(); }
    }

    private async Task<IReadOnlyList<AccountDeletionTombstone>> ReadUnsafeAsync(CancellationToken cancellationToken)
    {
        if (!File.Exists(LedgerPath)) return Array.Empty<AccountDeletionTombstone>();
        var result = new List<AccountDeletionTombstone>();
        foreach (var line in await File.ReadAllLinesAsync(LedgerPath, cancellationToken))
        {
            if (string.IsNullOrWhiteSpace(line)) continue;
            try
            {
                var item = JsonSerializer.Deserialize<AccountDeletionTombstone>(line);
                if (item is null
                    || item.SchemaVersion != SchemaVersion
                    || item.RequestId == Guid.Empty
                    || item.OwnerKey.Length != 64
                    || item.OwnerKey.Any(character => !Uri.IsHexDigit(character)))
                    throw new InvalidOperationException("The account-deletion tombstone ledger contains an invalid record.");
                result.Add(item);
            }
            catch (JsonException)
            {
                // A partial/corrupt line is never ignored by replay callers: expose a sentinel so
                // startup fails closed instead of declaring the tombstone set complete.
                throw new InvalidOperationException("The account-deletion tombstone ledger contains an invalid record.");
            }
        }
        return result;
    }
}
