using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Input;
using DodoSSH.Client.App.Views;

namespace DodoSSH.Client.App.Layout.Tests;

/// <summary>
/// The harness measuring itself.
/// </summary>
/// <remarks>
/// A clipping detector that never fires is worse than no detector, because it reads as a guarantee. So the
/// deliberately-broken window below is the most important test in this file: it is the one that proves the
/// green ones mean something. This is the same practice §5 of the handoff describes — a test proves nothing
/// until it has been seen to fail — applied to the instrument rather than to the code.
/// </remarks>
public sealed class LayoutHarnessTests
{
    private static CancellationToken Token => TestContext.Current.CancellationToken;

    [Fact]
    public async Task AWindowWithRoomToSpare_ReportsNothing()
    {
        await LayoutHarness.OnTheUiThreadAsync(
            () =>
            {
                var window = LayoutHarness.HostAtMinimumSize(
                    new Button { Content = "Save" },
                    LayoutHarness.MinimumWidth,
                    LayoutHarness.MinimumHeight);

                try
                {
                    LayoutHarness.Unreachable(window).ShouldBeEmpty();
                }
                finally
                {
                    window.Close();
                }
            },
            Token);
    }

    [Fact]
    public async Task AButtonPushedPastTheBottomEdge_IsReportedByName()
    {
        // The instrument's own calibration. A stack taller than its window is exactly the shape of the
        // defect the vault column is one editor away from, and if this passes silently the harness is
        // decoration.
        await LayoutHarness.OnTheUiThreadAsync(
            () =>
            {
                var stack = new StackPanel();

                for (var i = 0; i < 8; i++)
                {
                    stack.Children.Add(new Button { Content = i == 7 ? "Save" : $"filler {i}" });
                }

                var window = LayoutHarness.HostAtMinimumSize(stack, 300, 120);

                try
                {
                    var faults = LayoutHarness.Unreachable(window);

                    faults.ShouldNotBeEmpty();
                    faults.ShouldContain(fault => fault.Contains("'Save'", StringComparison.Ordinal));
                }
                finally
                {
                    window.Close();
                }
            },
            Token);
    }

    [Fact]
    public async Task AListLongerThanItsViewport_IsNotAFault()
    {
        // The exemption that keeps this harness usable. Scrolling is how a list is supposed to handle more
        // rows than fit; without this the host list would fail the moment it had content.
        await LayoutHarness.OnTheUiThreadAsync(
            () =>
            {
                var stack = new StackPanel();

                for (var i = 0; i < 40; i++)
                {
                    stack.Children.Add(new Button { Content = $"row {i}" });
                }

                var window = LayoutHarness.HostAtMinimumSize(
                    new ScrollViewer { Content = stack },
                    300,
                    120);

                try
                {
                    LayoutHarness.Unreachable(window).ShouldBeEmpty();
                }
                finally
                {
                    window.Close();
                }
            },
            Token);
    }

    [Fact]
    public async Task TheWholeWindowsXamlParses()
    {
        // Constructing it runs InitializeComponent, so this is what catches malformed XAML, a style selector
        // that no longer resolves or a converter reference that has gone stale. Cheap, and it covers the
        // whole file including the setup cards no other test here touches.
        //
        // Constructed and never shown, deliberately — see WhyTheWindowItselfIsNeverShown.
        await LayoutHarness.OnTheUiThreadAsync(
            () =>
            {
                var window = new MainWindow();

                try
                {
                    window.Content.ShouldNotBeNull();
                }
                finally
                {
                    window.Close();
                }
            },
            Token);
    }

    [Fact]
    public async Task WhyTheWindowItselfIsNeverShown()
    {
        // Measured, not assumed, and pinned here so nobody spends an afternoon rediscovering it.
        //
        // Showing MainWindow attaches the terminal's NativeWebView, whose Win32 adapter initialises WebView2
        // on attach — and WebView2 refuses an MTA thread, which is exactly why Program.Main is [STAThread].
        // A HeadlessUnitTestSession owns its dispatcher thread and does not offer an apartment choice, so
        // the whole window cannot be laid out here at any size.
        //
        // That is the reason this harness measures the extracted controls rather than MainWindow: each of
        // them is a part with a height budget to blow, and none of them has a native child window in it. If a
        // future Avalonia makes the adapter lazy, this test starts failing and the harness can be widened.
        //
        // All of which is a fact about Windows. WebView2 is a Win32 component and RPC_E_CHANGED_MODE is a COM
        // error code; on Linux the terminal's adapter is a different implementation with no apartment to
        // disagree about, so showing the window simply works and Should.Throw has nothing to catch. Skipped
        // rather than rewritten to accept either outcome, because the assertion is the documentation here —
        // a version that passed on both platforms would have stopped recording the constraint it exists to
        // record. What the rest of the suite relies on, that the harness measures extracted controls, holds
        // on every platform regardless.
        if (!OperatingSystem.IsWindows())
        {
            Assert.Skip("WebView2's apartment requirement, and the COM error it raises, are Windows-only.");
        }

        await LayoutHarness.OnTheUiThreadAsync(
            () =>
            {
                var window = new MainWindow();

                try
                {
                    var showing = Should.Throw<COMException>(() => LayoutHarness.Settle(
                        window,
                        LayoutHarness.MinimumWidth,
                        LayoutHarness.MinimumHeight));

                    // RPC_E_CHANGED_MODE. Asserted on the code rather than the message so a localised
                    // Windows does not break the build.
                    showing.HResult.ShouldBe(unchecked((int)0x80010106));
                }
                finally
                {
                    window.Close();
                }
            },
            Token);
    }

    [Fact]
    public async Task TheHarnessMeasuresTheSizeTheWindowDeclares()
    {
        // Pins the two constants against the XAML. A harness measuring a size the window lets itself
        // be dragged to something smaller would be certifying a size no user is held to.
        await LayoutHarness.OnTheUiThreadAsync(
            () =>
            {
                var window = new MainWindow();

                try
                {
                    window.MinWidth.ShouldBe(LayoutHarness.MinimumWidth);
                    window.MinHeight.ShouldBe(LayoutHarness.MinimumHeight);
                }
                finally
                {
                    window.Close();
                }
            },
            Token);
    }
}
