namespace Nexus.Api.Services;

/// <summary>
/// Continuously folds bounded Gateway chat/run events into the durable Nexus
/// run projection. Re-processing is safe because per-run sequence cursors and
/// persisted terminal/gap event ids reject duplicates.
/// </summary>
public sealed class OpenClawRunEventReconciler(
    IGatewayConnector connector,
    IServiceScopeFactory scopeFactory,
    ILogger<OpenClawRunEventReconciler> logger) : BackgroundService
{
    private static readonly TimeSpan PollInterval = TimeSpan.FromMilliseconds(750);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        string? cursor = null;

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                var snapshot = connector.GetRecentEvents(500)
                    .OrderBy(item => item.ReceivedAt)
                    .ThenBy(item => item.Sequence)
                    .ThenBy(OpenClawEventIdentity.Create, StringComparer.Ordinal)
                    .ToList();
                var startIndex = cursor is null
                    ? 0
                    : snapshot.FindIndex(item =>
                        string.Equals(
                            OpenClawEventIdentity.Create(item),
                            cursor,
                            StringComparison.Ordinal)) + 1;
                if (startIndex <= 0 && cursor is not null)
                {
                    // The bounded buffer rolled over. Re-process what remains;
                    // run-level sequence cursors and persisted event ids dedupe it.
                    startIndex = 0;
                }

                if (startIndex < snapshot.Count)
                {
                    await using var scope = scopeFactory.CreateAsyncScope();
                    var runs = scope.ServiceProvider.GetRequiredService<IOpenClawRunService>();
                    foreach (var gatewayEvent in snapshot.Skip(startIndex))
                    {
                        await runs.ReconcileAsync(gatewayEvent, stoppingToken);
                        cursor = OpenClawEventIdentity.Create(gatewayEvent);
                    }
                }
                else if (snapshot.Count > 0)
                {
                    cursor = OpenClawEventIdentity.Create(snapshot[^1]);
                }
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
            {
                break;
            }
            catch (Exception exception)
            {
                logger.LogWarning(
                    exception,
                    "Could not reconcile the latest OpenClaw run events; retrying without dropping the durable projection.");
            }

            await Task.Delay(PollInterval, stoppingToken);
        }
    }
}
