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

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

/// <summary>
/// Verifies that the <see cref="UpdateRouter"/> delegates to the wizard when
/// the GM has an active (non-expired) draft, and falls through to normal
/// handling when no draft is active. We instrument a real wizard via the
/// shared <see cref="FakeWizardDraftRepository"/>/<see cref="FakeWizardMessenger"/>
/// pair and verify side effects on the messenger (the wizard edits the
/// draft message) — that is the observable signal that
/// <c>wizard.HandleUpdateAsync</c> was called.
/// </summary>
public sealed class UpdateRouterDelegationTests
{
    [Fact]
    public async Task ActiveDraft_Existing_RoutesToWizard()
    {
        var sut = BuildRouter(out var drafts, out var messenger);

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

        var update = TextUpdate("Curse of Strahd", ownerId: long.Parse(draft.OwnerId, System.Globalization.CultureInfo.InvariantCulture));

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

        // Wizard edits the draft message when it processes a title.
        Assert.NotEmpty(messenger.Edits);
    }

    [Fact]
    public async Task ActiveDraft_Existing_OnCallback_AlsoRoutesToWizard()
    {
        var sut = BuildRouter(out var drafts, out _);

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

        // "wizard:cancel" — wizard owns the cancel callback. The router
        // delegates control-callbacks (resume/reset) but lets the wizard
        // handle wizard:* callbacks.
        var update = CallbackUpdate(WizardCallbackData.Cancel(), ownerId: long.Parse(draft.OwnerId, System.Globalization.CultureInfo.InvariantCulture));

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

        // Cancel deletes the draft via the wizard.
        Assert.Contains(draft.Id, drafts.DeletedIds);
    }

    [Fact]
    public async Task NoActiveDraft_FallsThrough()
    {
        var sut = BuildRouter(out _, out var messenger);

        // No active draft → router should NOT call the wizard. It will
        // attempt to run the /help command via the fallback command path.
        // We send a /help message; the router has no draft to act on.
        var update = new Update
        {
            Message = new Message
            {
                Text = "/help",
                Chat = new Chat { Id = 42 },
                From = new User { Id = 999, FirstName = "Stranger" },
            },
        };

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

        // The wizard should not have edited anything (no draft was active).
        Assert.Empty(messenger.Edits);
    }

    private static UpdateRouter BuildRouter(
        out FakeWizardDraftRepository drafts,
        out FakeWizardMessenger messenger)
    {
        drafts = new FakeWizardDraftRepository();
        messenger = new FakeWizardMessenger();

        // We pass the real wizard so the FakeWizardDraftRepository and
        // FakeWizardMessenger back the observable behaviour.
        var wizard = new GameCreationWizard(drafts, messenger, NullLogger<GameCreationWizard>.Instance);

        // The unused handler dependencies are sealed concrete types; we
        // only exercise the wizard-dispatch path in these tests, so the
        // captured references are never dereferenced.
        var router = new UpdateRouter(
            rsvpHandler: null!,
            createSessionHandler: null!,
            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: Substitute.For<ITelegramBotClient>(),
            configuration: Substitute.For<IConfiguration>(),
            logger: NullLogger<UpdateRouter>.Instance);

        return router;
    }
}
