using System.Diagnostics;
using CloakBrowser.Human;
using CloakBrowser.Tests.Wrappers; // Fake / FakeProxy test infrastructure
using Microsoft.Playwright;
using Xunit;

namespace CloakBrowser.Tests.Human;

/// <summary>
/// Ports Python <c>TestPointerEventsFailOpen</c>: the pointer-events check must "fail open".
/// When it cannot run (bounding box / evaluate throws - stale handle, execution context
/// destroyed), the check returns promptly instead of blocking until the timeout. But a
/// genuine "covered" result (hit == false) must still raise
/// <see cref="ElementNotReceivingEventsError"/>.
/// </summary>
public class PointerEventsFailOpenTests
{
    // Build a Task<PointerResult?> handler value for the FakeProxy. PointerResult is
    // internal (visible to tests via InternalsVisibleTo), so we can construct one.
    private static Task<Actionability.PointerResult?> CoveredResult(string covering) =>
        Task.FromResult<Actionability.PointerResult?>(
            new Actionability.PointerResult { Hit = false, Covering = covering });

    // --- Locator (selector) variant ----------------------------------------

    [Fact]
    public async Task Locator_fails_open_when_evaluate_throws()
    {
        var (locator, locRec) = Fake.Of<ILocator>();
        locRec.On("First", locator);
        locRec.On("BoundingBoxAsync", _ => throw new PlaywrightException("no element"));
        locRec.On("EvaluateAsync", _ => throw new PlaywrightException("execution context destroyed"));

        var (page, pageRec) = Fake.Of<IPage>();
        pageRec.On("Locator", locator);

        var sw = Stopwatch.StartNew();
        // Must NOT throw and must return promptly (well under the 2000ms timeout).
        await Actionability.CheckPointerEventsAsync(page, "#x", 100, 100, timeoutMs: 2000);
        sw.Stop();

        Assert.True(sw.ElapsedMilliseconds < 500,
            $"fail-open should return promptly, took {sw.ElapsedMilliseconds}ms");
    }

    [Fact]
    public async Task Locator_raises_when_covered()
    {
        var (locator, locRec) = Fake.Of<ILocator>();
        locRec.On("First", locator);
        locRec.On("BoundingBoxAsync", Task.FromResult<LocatorBoundingBoxResult?>(
            new LocatorBoundingBoxResult { X = 0, Y = 0, Width = 10, Height = 10 }));
        locRec.On("EvaluateAsync", _ => CoveredResult("DIV"));

        var (page, pageRec) = Fake.Of<IPage>();
        pageRec.On("Locator", locator);

        var ex = await Assert.ThrowsAsync<ElementNotReceivingEventsError>(
            () => Actionability.CheckPointerEventsAsync(page, "#x", 5, 5, timeoutMs: 200));
        Assert.Contains("DIV", ex.Message);
    }

    // --- ElementHandle variant ----------------------------------------------

    [Fact]
    public async Task Handle_fails_open_when_evaluate_throws()
    {
        var (el, elRec) = Fake.Of<IElementHandle>();
        elRec.On("BoundingBoxAsync", _ => throw new PlaywrightException("stale handle"));
        elRec.On("EvaluateAsync", _ => throw new PlaywrightException("execution context destroyed"));

        var sw = Stopwatch.StartNew();
        await Actionability.CheckPointerEventsHandleAsync(el, 100, 100, timeoutMs: 2000);
        sw.Stop();

        Assert.True(sw.ElapsedMilliseconds < 500,
            $"fail-open should return promptly, took {sw.ElapsedMilliseconds}ms");
    }

    [Fact]
    public async Task Handle_raises_when_covered()
    {
        var (el, elRec) = Fake.Of<IElementHandle>();
        elRec.On("BoundingBoxAsync", Task.FromResult<ElementHandleBoundingBoxResult?>(
            new ElementHandleBoundingBoxResult { X = 0, Y = 0, Width = 10, Height = 10 }));
        elRec.On("EvaluateAsync", _ => CoveredResult("SPAN"));

        var ex = await Assert.ThrowsAsync<ElementNotReceivingEventsError>(
            () => Actionability.CheckPointerEventsHandleAsync(el, 5, 5, timeoutMs: 200));
        Assert.Contains("SPAN", ex.Message);
    }
}
