using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;

namespace GmRelay.Bot.Infrastructure.Telegram;

/// <summary>
/// Long polling loop for Telegram Bot API.
/// Stateless — all state is in PostgreSQL. Safe to restart at any time.
/// </summary>
public sealed class TelegramBotService(
    ITelegramUpdateSource updateSource,
    ITelegramUpdateHandler updateHandler,
    ILogger<TelegramBotService> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation("Telegram bot polling started");

        var offset = await GetStartupOffsetAsync(stoppingToken);

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                var updates = await updateSource.GetUpdatesAsync(
                    offset: offset,
                    timeout: 30,
                    allowedUpdates: [UpdateType.Message, UpdateType.CallbackQuery],
                    cancellationToken: stoppingToken);

                foreach (var update in updates)
                {
                    try
                    {
                        await updateHandler.RouteAsync(update, stoppingToken);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "Error handling update {UpdateId}", update.Id);
                    }

                    offset = update.Id + 1;
                }
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
            {
                break;
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Polling error, retrying in 5s");
                await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
            }
        }

        logger.LogInformation("Telegram bot polling stopped");
    }

    private async Task<int> GetStartupOffsetAsync(CancellationToken stoppingToken)
    {
        try
        {
            var pending = await updateSource.GetUpdatesAsync(
                offset: -1,
                limit: 1,
                cancellationToken: stoppingToken);

            if (pending.Length == 0)
            {
                return 0;
            }

            var startupOffset = pending[^1].Id + 1;
            logger.LogInformation(
                "Skipping pending updates through {LastPendingUpdateId}; starting polling from offset {StartupOffset}",
                pending[^1].Id,
                startupOffset);

            return startupOffset;
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Failed to determine startup offset, continuing from offset 0");
            return 0;
        }
    }
}
