using System.Threading;
using System.Threading.Tasks;
using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
using GmRelay.Bot.Infrastructure.Telegram;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using Telegram.Bot;
using Telegram.Bot.Requests.Abstractions;
using Telegram.Bot.Types;
using BotCreateSessionHandler = GmRelay.Bot.Features.Sessions.CreateSession.CreateSessionHandler;
using static GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard.WizardTestFakes;

namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;

/// <summary>
/// When the user sends <c>/newsession</c> while a non-expired draft already
/// exists, the router delegates the update to the wizard (the wizard owns
/// every update while a draft is active). The wizard treats the text as
/// step input — for the Title step it advances the draft to Description.
/// This is the observable contract that this test pins down.
/// </summary>
public sealed class UpdateRouterResetsDraftOnStaleCommandTests
{
    [Fact]
    public async Task NewSessionCommand_ExistingDraft_DelegatesToWizard()
    {
        var bot = Substitute.For<ITelegramBotClient>();
        var (sut, drafts, messenger) = BuildRouter(bot);

        var draft = NewDraft(WizardStepNames.Title);
        drafts.Seed(draft);

        var update = new Update
        {
            Message = new Message
            {
                Text = "/newsession",
                Chat = new Chat { Id = draft.ChatId },
                From = new User { Id = draft.OwnerTelegramId, FirstName = "GM" },
            },
        };

        await sut.RouteAsync(update, CancellationToken.None);

        // The router delegates to the wizard, which edits the draft
        // message as the Title step accepts the input and advances to
        // Description. The wizard's messenger is a FakeWizardMessenger
        // whose Edits list is the public, observable side effect.
        Assert.NotEmpty(messenger.Edits);
        // The bot.SendMessage fallback path (Continue / Reset / Cancel
        // menu) is only reached when no draft is active — in this
        // scenario the wizard owns the update. We assert it was NOT
        // taken here.
        await bot.DidNotReceiveWithAnyArgs().SendRequest(default(IRequest<Message>)!, default);
    }

    private static (UpdateRouter sut, FakeWizardDraftRepository drafts, FakeWizardMessenger messenger) BuildRouter(
        ITelegramBotClient bot)
    {
        var drafts = new FakeWizardDraftRepository();
        var messenger = new FakeWizardMessenger();
        var wizard = new GameCreationWizard(drafts, messenger, NullLogger<GameCreationWizard>.Instance);

        // Real Bot-side CreateSessionHandler — the test relies on
        // StartWizardAsync returning null when an active draft exists.
        // We pass null! for the shared handler since the active-draft
        // path never touches it.
        var createSessionHandler = new BotCreateSessionHandler(
            drafts,
            shared: null!,
            messenger,
            NullLogger<BotCreateSessionHandler>.Instance);

        var sut = new UpdateRouter(
            rsvpHandler: null!,
            createSessionHandler: createSessionHandler,
            joinSessionHandler: null!,
            leaveSessionHandler: null!,
            promoteWaitlistedPlayerHandler: null!,
            cancelSessionHandler: null!,
            deleteSessionHandler: null!,
            listSessionsHandler: null!,
            exportCalendarHandler: null!,
            initiateRescheduleHandler: null!,
            rescheduleTimeInputHandler: null!,
            rescheduleVoteHandler: null!,
            wizard: wizard,
            drafts: drafts,
            bot: bot,
            configuration: Substitute.For<IConfiguration>(),
            logger: NullLogger<UpdateRouter>.Instance);

        return (sut, drafts, messenger);
    }
}
