using System;
using System.Text.Json;
using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
using static GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard.WizardTestFakes;

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

/// <summary>
/// Verifies the wizard's input validation: invalid input stays on the same
/// step and re-renders with an error prefix. The repository is NOT called
/// with a step change.
/// </summary>
public sealed class GameCreationWizardValidationTests
{
    [Fact]
    public async Task EmptyTitle_StaysOnTitleStep()
    {
        var wizard = BuildWizard(out var drafts, out _);
        var draft = NewDraft(WizardStepNames.Title);
        drafts.Seed(draft);

        await wizard.HandleUpdateAsync(TextUpdate("   "), draft, CancellationToken.None);

        Assert.Equal(WizardStepNames.Title, draft.Step);
    }

    [Fact]
    public async Task OverlongTitle_StaysOnTitleStep()
    {
        var wizard = BuildWizard(out var drafts, out _);
        var draft = NewDraft(WizardStepNames.Title);
        drafts.Seed(draft);

        var tooLong = new string('a', WizardStep.MaxTitleLength + 1);
        await wizard.HandleUpdateAsync(TextUpdate(tooLong), draft, CancellationToken.None);

        Assert.Equal(WizardStepNames.Title, draft.Step);
    }

    [Fact]
    public async Task PastDate_StaysOnDateTimeStep()
    {
        var wizard = BuildWizard(out var drafts, out _);
        var payload = new WizardPayload
        {
            Type = WizardCreationType.Single,
            Title = "T",
            System = "Dnd5e",
            DurationMinutes = 240,
        };
        var draft = NewDraft(WizardStepNames.DateTime, payload);
        drafts.Seed(draft);

        // 2020-01-01 is firmly in the past
        await wizard.HandleUpdateAsync(TextUpdate("01.01.2020 12:00"), draft, CancellationToken.None);

        Assert.Equal(WizardStepNames.DateTime, draft.Step);
    }

    [Fact]
    public async Task UnparseableDate_StaysOnDateTimeStep()
    {
        var wizard = BuildWizard(out var drafts, out _);
        var draft = NewDraft(WizardStepNames.DateTime);
        drafts.Seed(draft);

        await wizard.HandleUpdateAsync(TextUpdate("not a date"), draft, CancellationToken.None);

        Assert.Equal(WizardStepNames.DateTime, draft.Step);
    }

    [Fact]
    public async Task BadCoverUrl_StaysOnCoverStep()
    {
        var wizard = BuildWizard(out var drafts, out _);
        var payload = new WizardPayload
        {
            Type = WizardCreationType.Single,
            Title = "T",
            Description = "D",
        };
        var draft = NewDraft(WizardStepNames.Cover, payload);
        drafts.Seed(draft);

        await wizard.HandleUpdateAsync(TextUpdate("not a url"), draft, CancellationToken.None);

        Assert.Equal(WizardStepNames.Cover, draft.Step);
    }

    [Fact]
    public async Task ValidCoverUrl_AdvancesToSystem()
    {
        var wizard = BuildWizard(out var drafts, out _);
        var draft = NewDraft(WizardStepNames.Cover,
            new WizardPayload { Type = WizardCreationType.Single, Title = "T", Description = "D" });
        drafts.Seed(draft);

        await wizard.HandleUpdateAsync(TextUpdate("https://example.com/x.jpg"), draft, CancellationToken.None);

        Assert.Equal(WizardStepNames.System, draft.Step);
    }

    [Fact]
    public async Task SkipCover_Dash_AdvancesToSystem()
    {
        var wizard = BuildWizard(out var drafts, out _);
        var draft = NewDraft(WizardStepNames.Cover,
            new WizardPayload { Type = WizardCreationType.Single, Title = "T", Description = "D" });
        drafts.Seed(draft);

        await wizard.HandleUpdateAsync(TextUpdate("-"), draft, CancellationToken.None);

        Assert.Equal(WizardStepNames.System, draft.Step);
    }

    [Theory]
    [InlineData("0")]
    [InlineData("51")]
    [InlineData("not a number")]
    public async Task OutOfRangeCapacity_StaysOnCapacityStep(string input)
    {
        var wizard = BuildWizard(out var drafts, out _);
        var draft = NewDraft(WizardStepNames.Capacity,
            new WizardPayload
            {
                Type = WizardCreationType.Single,
                Title = "T",
                System = "Dnd5e",
                DurationMinutes = 240,
                Single = new WizardSingleInput { ScheduledAt = DateTimeOffset.UtcNow.AddDays(1) },
            });
        drafts.Seed(draft);

        await wizard.HandleUpdateAsync(TextUpdate(input), draft, CancellationToken.None);

        Assert.Equal(WizardStepNames.Capacity, draft.Step);
    }

    [Theory]
    [InlineData("0")]
    [InlineData("13")]
    [InlineData("not-a-duration")]
    public async Task OutOfRangeDuration_StaysOnDurationStep(string input)
    {
        var wizard = BuildWizard(out var drafts, out _);
        var draft = NewDraft(WizardStepNames.Duration,
            new WizardPayload { Type = WizardCreationType.Single, Title = "T", System = "Dnd5e" });
        drafts.Seed(draft);

        await wizard.HandleUpdateAsync(TextUpdate(input), draft, CancellationToken.None);

        Assert.Equal(WizardStepNames.Duration, draft.Step);
    }

    [Fact]
    public async Task EmptyDescription_SkipDash_AdvancesToCover()
    {
        var wizard = BuildWizard(out var drafts, out _);
        var draft = NewDraft(WizardStepNames.Description,
            new WizardPayload { Type = WizardCreationType.Single, Title = "T" });
        drafts.Seed(draft);

        await wizard.HandleUpdateAsync(TextUpdate("-"), draft, CancellationToken.None);

        Assert.Equal(WizardStepNames.Cover, draft.Step);
    }

    [Fact]
    public async Task TextOnSystem_OtherBranch_AdvancesToDuration()
    {
        // The wizard's System step offers an "Другое… ✏️" choice which arms the
        // step for free-text entry of a custom system name. Once armed
        // (i.e. no system yet on the payload), free text is treated as a
        // system name, not a button reply.
        var wizard = BuildWizard(out var drafts, out _);
        var draft = NewDraft(WizardStepNames.System,
            new WizardPayload { Type = WizardCreationType.Single, Title = "T" });
        drafts.Seed(draft);

        await wizard.HandleUpdateAsync(TextUpdate("CustomSystem"), draft, CancellationToken.None);

        Assert.Equal(WizardStepNames.Duration, draft.Step);
    }
}
