using System.Reflection;
using GmRelay.Bot.Infrastructure.Telegram;
using Microsoft.Extensions.Logging.Abstractions;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;

namespace GmRelay.Bot.Tests.Infrastructure.Telegram;

public sealed class TelegramBotServiceTests
{
    [Fact]
    public async Task ExecuteAsync_ShouldStartPollingAfterLastPendingUpdate()
    {
        using var cts = new CancellationTokenSource();
        var updateSource = new FakeTelegramUpdateSource(cts);
        var updateHandler = new FakeTelegramUpdateHandler();
        var service = new TelegramBotService(
            updateSource,
            updateHandler,
            NullLogger<TelegramBotService>.Instance);

        await InvokeExecuteAsync(service, cts.Token);

        Assert.Empty(updateHandler.HandledUpdates);
        Assert.Collection(
            updateSource.Calls,
            call =>
            {
                Assert.Equal(-1, call.Offset);
                Assert.Equal(1, call.Limit);
                Assert.Null(call.Timeout);
                Assert.Null(call.AllowedUpdates);
            },
            call =>
            {
                Assert.Equal(43, call.Offset);
                Assert.Null(call.Limit);
                Assert.Equal(30, call.Timeout);
                Assert.Equal([UpdateType.Message, UpdateType.CallbackQuery], call.AllowedUpdates);
            });
    }

    private static async Task InvokeExecuteAsync(TelegramBotService service, CancellationToken cancellationToken)
    {
        var executeAsync = typeof(TelegramBotService).GetMethod(
            "ExecuteAsync",
            BindingFlags.Instance | BindingFlags.NonPublic);

        Assert.NotNull(executeAsync);

        var task = executeAsync.Invoke(service, [cancellationToken]) as Task;
        Assert.NotNull(task);

        await task;
    }

    private sealed class FakeTelegramUpdateHandler : ITelegramUpdateHandler
    {
        public List<Update> HandledUpdates { get; } = [];

        public Task RouteAsync(Update update, CancellationToken ct)
        {
            HandledUpdates.Add(update);
            return Task.CompletedTask;
        }
    }

    private sealed class FakeTelegramUpdateSource(CancellationTokenSource cts) : ITelegramUpdateSource
    {
        public List<PollCall> Calls { get; } = [];

        public Task<Update[]> GetUpdatesAsync(
            int offset,
            int? limit = null,
            int? timeout = null,
            IEnumerable<UpdateType>? allowedUpdates = null,
            CancellationToken cancellationToken = default)
        {
            Calls.Add(new PollCall(offset, limit, timeout, allowedUpdates?.ToArray()));

            return Calls.Count switch
            {
                1 => Task.FromResult(new[] { new Update { Id = 42 } }),
                2 => ReturnAndCancelAsync(),
                _ => throw new InvalidOperationException("Unexpected polling call.")
            };
        }

        private Task<Update[]> ReturnAndCancelAsync()
        {
            cts.Cancel();
            return Task.FromResult(Array.Empty<Update>());
        }
    }

    private sealed record PollCall(
        int Offset,
        int? Limit,
        int? Timeout,
        UpdateType[]? AllowedUpdates);
}
