using Microsoft.Playwright;

namespace Books.E2E.Tests.Actions;

/// <summary>
/// BDD "Then" assertions - verification and expectations.
/// </summary>
public class ThenAssertions
{
    private readonly ActionContext _context;

    internal ThenAssertions(ActionContext context)
    {
        _context = context;
    }

    /// <summary>
    /// Asserts that the user is redirected to the dashboard.
    /// </summary>
    public async Task<ThenAssertions> UserIsRedirectedToDashboard()
    {
        Console.WriteLine("[Then] Expecting redirect to dashboard");

        // Wait for navigation to complete
        await _context.Page.WaitForURLAsync(
            url => url.EndsWith("/") || url.Contains("dashboard"),
            new PageWaitForURLOptions { Timeout = 10000 });

        var currentUrl = _context.Page.Url;
        currentUrl.Should().Match(u => u.EndsWith("/") || u.Contains("dashboard"),
            "User should be redirected to dashboard");

        Console.WriteLine($"[Then] Successfully on dashboard: {currentUrl}");
        return this;
    }

    /// <summary>
    /// Asserts that a success message/result is shown.
    /// </summary>
    public async Task<ThenAssertions> UserSeesSuccessMessage()
    {
        Console.WriteLine("[Then] Expecting success message");

        // Wait for Ant Design success result
        var successResult = _context.Page.Locator(".ant-result-success");
        await Assertions.Expect(successResult).ToBeVisibleAsync(new() { Timeout = 10000 });

        Console.WriteLine("[Then] Success message visible");
        return this;
    }

    /// <summary>
    /// Asserts that the company was created by verifying the success screen shows the company name.
    /// </summary>
    public async Task<ThenAssertions> CompanyIsCreated()
    {
        Console.WriteLine("[Then] Verifying company was created");

        // Verify the success screen shows the company name
        if (!string.IsNullOrEmpty(_context.CompanyName))
        {
            var successText = _context.Page.Locator(".ant-result-title");
            await Assertions.Expect(successText).ToContainTextAsync(_context.CompanyName, new() { Timeout = 5000 });
        }

        Console.WriteLine("[Then] Company creation verified via UI");
        return this;
    }

    /// <summary>
    /// Asserts that a validation error message is shown.
    /// </summary>
    public async Task<ThenAssertions> ValidationErrorIsShown(string expectedMessage)
    {
        Console.WriteLine($"[Then] Expecting validation error: {expectedMessage}");

        // Wait for Ant Design form validation error
        var errorLocator = _context.Page.Locator(".ant-form-item-explain-error");
        await Assertions.Expect(errorLocator.First).ToBeVisibleAsync(new() { Timeout = 5000 });

        // Check if any error contains the expected message
        var errors = await errorLocator.AllTextContentsAsync();
        errors.Should().Contain(e => e.Contains(expectedMessage, StringComparison.OrdinalIgnoreCase),
            $"Expected validation error containing '{expectedMessage}'");

        Console.WriteLine($"[Then] Validation error found: {string.Join(", ", errors)}");
        return this;
    }

    /// <summary>
    /// Asserts that an error toast/notification is shown.
    /// </summary>
    public async Task<ThenAssertions> ErrorMessageIsShown(string expectedMessage)
    {
        Console.WriteLine($"[Then] Expecting error message: {expectedMessage}");

        // Wait for Ant Design message error
        var errorMessage = _context.Page.Locator(".ant-message-error");
        await Assertions.Expect(errorMessage).ToBeVisibleAsync(new() { Timeout = 5000 });

        var text = await errorMessage.TextContentAsync();
        text.Should().Contain(expectedMessage);

        Console.WriteLine($"[Then] Error message found: {text}");
        return this;
    }

    /// <summary>
    /// Asserts that the user is on a specific page.
    /// </summary>
    public async Task<ThenAssertions> UserIsOnPage(string expectedPath)
    {
        Console.WriteLine($"[Then] Expecting to be on page: {expectedPath}");

        await _context.Page.WaitForURLAsync(
            url => url.Contains(expectedPath),
            new PageWaitForURLOptions { Timeout = 5000 });

        var currentUrl = _context.Page.Url;
        currentUrl.Should().Contain(expectedPath);

        Console.WriteLine($"[Then] On expected page: {currentUrl}");
        return this;
    }

    /// <summary>
    /// Asserts that specific text is visible on the page.
    /// </summary>
    public async Task<ThenAssertions> TextIsVisible(string text)
    {
        Console.WriteLine($"[Then] Expecting text to be visible: {text}");

        var locator = _context.Page.GetByText(text);
        await Assertions.Expect(locator.First).ToBeVisibleAsync(new() { Timeout = 5000 });

        Console.WriteLine($"[Then] Text is visible: {text}");
        return this;
    }

    /// <summary>
    /// Chain multiple Then assertions with "And".
    /// </summary>
    public ThenAssertions And => this;
}

/// <summary>
/// Helper for handling eventual consistency in assertions.
/// </summary>
public static class Eventually
{
    public static async Task<T?> GetAsync<T>(
        Func<Task<T?>> getter,
        TimeSpan? timeout = null,
        TimeSpan? pollInterval = null) where T : class
    {
        var actualTimeout = timeout ?? TimeSpan.FromSeconds(10);
        var actualPollInterval = pollInterval ?? TimeSpan.FromMilliseconds(200);

        var deadline = DateTime.UtcNow + actualTimeout;

        while (DateTime.UtcNow < deadline)
        {
            var result = await getter();
            if (result != null)
            {
                return result;
            }

            await Task.Delay(actualPollInterval);
        }

        return null;
    }

    public static async Task AssertAsync(
        Func<Task<bool>> condition,
        TimeSpan? timeout = null,
        string? message = null)
    {
        var actualTimeout = timeout ?? TimeSpan.FromSeconds(5);
        var deadline = DateTime.UtcNow + actualTimeout;

        while (DateTime.UtcNow < deadline)
        {
            if (await condition())
            {
                return;
            }

            await Task.Delay(TimeSpan.FromMilliseconds(200));
        }

        throw new TimeoutException(message ?? $"Condition not met within {actualTimeout.TotalSeconds}s");
    }
}
