using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.VisualTree;
using DodoSSH.Client.App.Views;
using DodoSSH.Client.Session;
using DodoSSH.Client.Shell.ViewModels;
using DodoSSH.Client.Ssh;
using DodoSSH.Client.Storage;
using DodoSSH.Client.Terminal;
using NSubstitute;

namespace DodoSSH.Client.App.Layout.Tests;

/// <summary>
/// The titlebar's own search pill still opens the quick-connect palette after v5b's redraw.
/// </summary>
/// <remarks>
/// A narrow suite on purpose: <see cref="LayoutHarnessTests"/> already holds the bar to its declared
/// height and proves nothing inside it is unreachable, and the search pill's own accent-on-hover border is
/// a style rule with nothing to assert headlessly. What is worth a gesture is the one thing a fidelity pass
/// could quietly break without any layout test noticing — the pill no longer opening what Ctrl+K opens.
/// </remarks>
public sealed class TitleBarTests : IAsyncLifetime
{
    private ClientCacheFactory caches = null!;
    private TerminalWorkspace workspace = null!;
    private MainWindowViewModel shell = null!;

    private static CancellationToken Token => TestContext.Current.CancellationToken;

    /// <inheritdoc />
    public ValueTask InitializeAsync()
    {
        caches = ClientCacheFactory.ForMemory($"titlebar-{Guid.CreateVersion7():N}");

        workspace = new TerminalWorkspace(
            new InMemoryTerminalAssetProvider(new Dictionary<string, TerminalAsset>(StringComparer.Ordinal)),
            Substitute.For<ISshConnectionFactory>(),
            TimeProvider.System);

        shell = new MainWindowViewModel(
            ClientPaths.Default,
            caches,
            workspace,
            new VaultKnownHostStore(),
            Substitute.For<IDeviceKeyStore>(),
            (_, _) => throw new NotSupportedException("nothing here signs in"),
            TimeProvider.System,
            Substitute.For<ISftpSessionFactory>())
        {
            // The search pill is disabled while locked — see TitleBar.axaml's IsEnabled binding — and this
            // is the only state that gesture is reachable in.
            State = ShellState.Unlocked,
        };

        return ValueTask.CompletedTask;
    }

    /// <inheritdoc />
    public async ValueTask DisposeAsync()
    {
        await shell.DisposeAsync();
        await workspace.DisposeAsync();
        caches.Dispose();
    }

    [Fact]
    public async Task ClickingTheSearchPill_OpensTheQuickConnectPalette()
    {
        await LayoutHarness.OnTheUiThreadAsync(
            () =>
            {
                var bar = new TitleBar { DataContext = shell };
                var window = LayoutHarness.HostAtMinimumSize(
                    bar, LayoutHarness.MinimumWidth, LayoutHarness.TitleBarHeight);

                try
                {
                    shell.IsSearching.ShouldBeFalse("nothing has been pressed yet");

                    var pill = bar.GetVisualDescendants()
                        .OfType<Button>()
                        .First(button => button.Classes.Contains("search"));

                    var centre = pill.TranslatePoint(
                        new Point(pill.Bounds.Width / 2, pill.Bounds.Height / 2), window)
                        ?? throw new InvalidOperationException("the pill is not in this window's tree");

                    window.MouseDown(centre, MouseButton.Left);
                    window.MouseUp(centre, MouseButton.Left);

                    shell.IsSearching.ShouldBeTrue(
                        "the pill is the click-through path Ctrl+K also opens — see ToggleSearchCommand");
                }
                finally
                {
                    window.Close();
                }
            },
            Token);
    }

    /// <remarks>
    /// The chip that says which chord opens the pill beside it. The design's own is 34 pixels wide because
    /// the design's own label is ⌘K — two glyphs — and this build substitutes "CTRL K", which at 10.5 mono is
    /// wider than that. It shipped clipped: the chip drew "CTRL" and half of the K, which reads as a rendering
    /// glitch rather than as a keyboard shortcut.
    ///
    /// Asserted as "the chip is at least as wide as its own text", not against a number. A pixel count would
    /// have to be re-derived by hand every time the font, the size or the wording moved, and the thing that
    /// actually matters is the relationship between the two.
    /// </remarks>
    [Fact]
    public async Task TheKeyboardChipIsWideEnoughForTheChordItNames()
    {
        await LayoutHarness.OnTheUiThreadAsync(
            () =>
            {
                var bar = new TitleBar { DataContext = shell };
                var window = LayoutHarness.HostAtMinimumSize(
                    bar, LayoutHarness.MinimumWidth, LayoutHarness.TitleBarHeight);

                try
                {
                    var label = bar.GetVisualDescendants()
                        .OfType<TextBlock>()
                        .Single(text => string.Equals(text.Text, "CTRL K", StringComparison.Ordinal));

                    var chip = label.GetVisualAncestors().OfType<Border>().First();

                    // Measured on a copy under an unbounded constraint, not read off the label in the tree.
                    // A TextBlock's own DesiredSize is already clipped to what it was given, so the laid-out
                    // one reports 34 inside a 34-pixel chip whether or not the text fits — which is exactly
                    // the state this test exists to fail on.
                    var natural = new TextBlock
                    {
                        Text = label.Text,
                        FontFamily = label.FontFamily,
                        FontSize = label.FontSize,
                        FontWeight = label.FontWeight,
                    };

                    natural.Measure(Size.Infinity);

                    natural.DesiredSize.Width.ShouldBeGreaterThan(0, "the chord is a real run of text");
                    chip.Bounds.Width.ShouldBeGreaterThanOrEqualTo(
                        natural.DesiredSize.Width,
                        "a chip narrower than its own label draws part of the chord and cuts the rest");
                }
                finally
                {
                    window.Close();
                }
            },
            Token);
    }
}
