using System;
using System.Linq;
using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
using Telegram.Bot.Types.ReplyMarkups;

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

/// <summary>
/// Verifies the shape of each step's rendered keyboard: which buttons are
/// present, where the Back/Cancel affordances sit, and that the title text
/// is non-empty. Tests use substring matching so they survive label tweaks
/// (e.g. emoji prefixes, suffix additions like "· 4 ч").
/// </summary>
public sealed class WizardStepRenderTests
{
    [Fact]
    public void TypeStep_HasBothChoicesAndCancel_ButNoBack()
    {
        var (text, kb) = Render(WizardStepNames.Type);

        Assert.False(string.IsNullOrWhiteSpace(text));
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("Одну игру", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Пул игр", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
        Assert.DoesNotContain(labels, l => l.Contains("Назад", StringComparison.Ordinal));
    }

    [Fact]
    public void TitleStep_HasBackAndCancel_ButNoChoiceButtons()
    {
        var (text, kb) = Render(WizardStepNames.Title);

        Assert.False(string.IsNullOrWhiteSpace(text));
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("Назад", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
    }

    [Fact]
    public void SystemStep_HasKnownSystemButtons()
    {
        var (text, kb) = Render(WizardStepNames.System);

        Assert.False(string.IsNullOrWhiteSpace(text));
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("D&D 5e", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Pathfinder 2e", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Call of Cthulhu", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("GURPS", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Fate", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Другое", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Пропустить", StringComparison.Ordinal));
    }

    [Fact]
    public void DurationStep_HasPresetButtons()
    {
        var (text, kb) = Render(WizardStepNames.Duration);

        Assert.False(string.IsNullOrWhiteSpace(text));
        var labels = ButtonLabels(kb);
        Assert.Contains("3 часа", labels);
        Assert.Contains("4 часа", labels);
        Assert.Contains("5 часов", labels);
        Assert.Contains("6 часов", labels);
    }

    [Fact]
    public void CapacityStep_HasWaitlistButtons()
    {
        var (text, kb) = Render(WizardStepNames.Capacity);

        Assert.False(string.IsNullOrWhiteSpace(text));
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("Waitlist вкл", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Без waitlist", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Без лимита", StringComparison.Ordinal));
    }

    [Fact]
    public void VisibilityStep_HasAllFourVisibilityOptions()
    {
        var (text, kb) = Render(WizardStepNames.Visibility);

        Assert.False(string.IsNullOrWhiteSpace(text));
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("Публичная в общем showcase", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Публичная в витрине клуба", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Только для членов клуба", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Выбрать клуб", StringComparison.Ordinal));
    }

    [Fact]
    public void PickClub_WithoutClubs_RendersEmptyHint()
    {
        var (text, kb) = WizardStep.Render(
            NewDraft(WizardStepNames.PickClub),
            new WizardPayload(),
            clubs: null);

        Assert.False(string.IsNullOrWhiteSpace(text));
        Assert.Contains("нет клубов", text, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void PickClub_WithOneClub_RendersClubButton()
    {
        var clubId = Guid.NewGuid();
        var (text, kb) = WizardStep.Render(
            NewDraft(WizardStepNames.PickClub),
            new WizardPayload(),
            new[] { new WizardClubOption(clubId, "Awesome Club") });

        Assert.False(string.IsNullOrWhiteSpace(text));
        Assert.Contains("Awesome Club", ButtonLabels(kb));
    }

    [Fact]
    public void PublishStep_HasPublishAndChatOnlyButtons()
    {
        var (text, kb) = Render(WizardStepNames.Publish);

        Assert.False(string.IsNullOrWhiteSpace(text));
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("Опубликовать", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Только в чате", StringComparison.Ordinal));
    }

    [Fact]
    public void ConfirmStep_HasCreateAndCancel()
    {
        var (text, kb) = Render(WizardStepNames.Confirm, new WizardPayload
        {
            Type = WizardCreationType.Single,
            Title = "My Game",
        });

        Assert.False(string.IsNullOrWhiteSpace(text));
        Assert.Contains("My Game", text);
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("Создать", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
    }

    [Fact]
    public void PoolSystemDuration_HasPresetsAndCustom()
    {
        var (text, kb) = Render(WizardStepNames.PoolSystemDuration);

        Assert.False(string.IsNullOrWhiteSpace(text));
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("D&D 5e", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Pathfinder 2e", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Call of Cthulhu", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("GURPS", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Другое", StringComparison.Ordinal));
    }

    [Fact]
    public void PoolAddSlots_HasAddAndDone_AndShowsCurrentCount()
    {
        var payload = new WizardPayload
        {
            Type = WizardCreationType.Pool,
            Title = "My Pool",
            Pool = new WizardPoolInput
            {
                Slots =
                {
                    new WizardSlotInput { MaxPlayers = 4 },
                    new WizardSlotInput { MaxPlayers = 5 },
                },
            },
        };
        var (text, kb) = WizardStep.Render(
            NewDraft(WizardStepNames.PoolAddSlots),
            payload);

        Assert.Contains("My Pool", text);
        Assert.Contains("2", text);
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("Добавить слот", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Готово", StringComparison.Ordinal));
    }

    [Fact]
    public void PoolSlotDateTime_HasBackAndCancel_ButNoChoiceButtons()
    {
        var (text, kb) = Render(WizardStepNames.PoolSlotDateTime);

        Assert.False(string.IsNullOrWhiteSpace(text));
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("Назад", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
    }

    [Fact]
    public void PoolSlotCapacity_HasWaitlistButtons()
    {
        var (text, kb) = Render(WizardStepNames.PoolSlotCapacity);

        Assert.False(string.IsNullOrWhiteSpace(text));
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("Waitlist вкл", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Без waitlist", StringComparison.Ordinal));
    }

    [Fact]
    public void PoolConfirm_HasCreatePoolAndCancel()
    {
        var payload = new WizardPayload
        {
            Type = WizardCreationType.Pool,
            Title = "Pool",
            System = "Dnd5e",
            DurationMinutes = 240,
            Pool = new WizardPoolInput
            {
                Slots =
                {
                    new WizardSlotInput { MaxPlayers = 4, Waitlist = true,  ScheduledAt = DateTimeOffset.UtcNow.AddDays(7) },
                    new WizardSlotInput { MaxPlayers = 5, Waitlist = false, ScheduledAt = DateTimeOffset.UtcNow.AddDays(14) },
                },
            },
        };
        var (text, kb) = WizardStep.Render(
            NewDraft(WizardStepNames.PoolConfirm),
            payload);

        Assert.Contains("Pool", text);
        Assert.Contains("2", text); // slot count
        var labels = ButtonLabels(kb);
        Assert.Contains(labels, l => l.Contains("Создать пул", StringComparison.Ordinal));
        Assert.Contains(labels, l => l.Contains("Отмена", StringComparison.Ordinal));
    }

    [Fact]
    public void Render_UnknownStep_Throws()
    {
        var draft = new WizardDraft { Step = "Bogus" };
        Assert.Throws<InvalidOperationException>(() => WizardStep.Render(draft, new WizardPayload()));
    }

    private static (string text, InlineKeyboardMarkup kb) Render(string step, WizardPayload? payload = null)
        => WizardStep.Render(NewDraft(step), payload ?? new WizardPayload());

    private static WizardDraft NewDraft(string step) => new()
    {
        Id = Guid.NewGuid(),
        ChatId = "42",
        Step = step,
        PayloadJson = "{}",
    };

    private static string[] ButtonLabels(InlineKeyboardMarkup kb) =>
        kb.InlineKeyboard
            .SelectMany(row => row.Select(b => b.Text))
            .ToArray();
}
