using System.Net;
using System.Text.RegularExpressions;

namespace CartWise.Web.Tests;

public class ShoppingListItemActionsTests : IClassFixture<CartWiseWebApplicationFactory>
{
    private readonly CartWiseWebApplicationFactory _factory;

    public ShoppingListItemActionsTests(CartWiseWebApplicationFactory factory)
    {
        _factory = factory;
    }

    [Fact]
    public async Task TogglePurchased_MarksItemPurchased()
    {
        var client = _factory.CreateClient();
        await WebTestHelpers.RegisterAsync(client, "toggle1@example.com", "Toggle User");
        await WebTestHelpers.CreateHouseholdAsync(client, "Toggle Household");
        await AddItemAsync(client, "Milk");

        var listHtml = await client.GetStringAsync("/list");
        var (id, rowVersion) = ExtractFirstItemInfo(listHtml);
        var token = WebTestHelpers.ExtractAntiForgeryToken(listHtml);

        var response = await client.PostAsync($"/list/items/{id}/toggle", new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["__RequestVerificationToken"] = token,
            ["rowVersion"] = rowVersion
        }));
        response.EnsureSuccessStatusCode();

        var updatedHtml = await client.GetStringAsync("/list");
        HtmlAssert.Contains("Purchased", updatedHtml);
    }

    [Fact]
    public async Task TogglePurchased_TwiceReturnsItemToNeeded()
    {
        var client = _factory.CreateClient();
        await WebTestHelpers.RegisterAsync(client, "toggle2@example.com", "Toggle User 2");
        await WebTestHelpers.CreateHouseholdAsync(client, "Toggle Household 2");
        await AddItemAsync(client, "Milk");

        var listHtml = await client.GetStringAsync("/list");
        var (id, rowVersion) = ExtractFirstItemInfo(listHtml);
        var token = WebTestHelpers.ExtractAntiForgeryToken(listHtml);

        await client.PostAsync($"/list/items/{id}/toggle", new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["__RequestVerificationToken"] = token,
            ["rowVersion"] = rowVersion
        }));

        var midHtml = await client.GetStringAsync("/list");
        var (_, secondRowVersion) = ExtractFirstItemInfo(midHtml);
        var midToken = WebTestHelpers.ExtractAntiForgeryToken(midHtml);

        await client.PostAsync($"/list/items/{id}/toggle", new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["__RequestVerificationToken"] = midToken,
            ["rowVersion"] = secondRowVersion
        }));

        var finalHtml = await client.GetStringAsync("/list");
        HtmlAssert.Contains("Needed", finalHtml);
    }

    [Fact]
    public async Task ToggleSkipped_MarksItemSkipped()
    {
        var client = _factory.CreateClient();
        await WebTestHelpers.RegisterAsync(client, "skip1@example.com", "Skip User");
        await WebTestHelpers.CreateHouseholdAsync(client, "Skip Household");
        await AddItemAsync(client, "Bread");

        var listHtml = await client.GetStringAsync("/list");
        var (id, rowVersion) = ExtractFirstItemInfo(listHtml);
        var token = WebTestHelpers.ExtractAntiForgeryToken(listHtml);

        var response = await client.PostAsync($"/list/items/{id}/skip", new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["__RequestVerificationToken"] = token,
            ["rowVersion"] = rowVersion
        }));
        response.EnsureSuccessStatusCode();

        var updatedHtml = await client.GetStringAsync("/list");
        HtmlAssert.Contains("Skipped", updatedHtml);
    }

    [Fact]
    public async Task DeleteItem_RemovesItFromTheList()
    {
        var client = _factory.CreateClient();
        await WebTestHelpers.RegisterAsync(client, "delete1@example.com", "Delete User");
        await WebTestHelpers.CreateHouseholdAsync(client, "Delete Household");
        await AddItemAsync(client, "Eggs");

        var listHtml = await client.GetStringAsync("/list");
        var (id, rowVersion) = ExtractFirstItemInfo(listHtml);
        var token = WebTestHelpers.ExtractAntiForgeryToken(listHtml);

        var response = await client.PostAsync($"/list/items/{id}/delete", new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["__RequestVerificationToken"] = token,
            ["rowVersion"] = rowVersion
        }));
        response.EnsureSuccessStatusCode();

        var updatedHtml = await client.GetStringAsync("/list");
        Assert.DoesNotContain("Eggs", updatedHtml);
        HtmlAssert.Contains("No items yet", updatedHtml);
    }

    [Fact]
    public async Task ToggleItem_FailsForAnItemInAnotherHousehold()
    {
        var clientA = _factory.CreateClient();
        var clientB = _factory.CreateClient();
        await WebTestHelpers.RegisterAsync(clientA, "isoalice@example.com", "Iso Alice");
        await WebTestHelpers.RegisterAsync(clientB, "isobob@example.com", "Iso Bob");
        await WebTestHelpers.CreateHouseholdAsync(clientA, "Iso Household A");
        await WebTestHelpers.CreateHouseholdAsync(clientB, "Iso Household B");
        await AddItemAsync(clientA, "Alice Only Item");

        var listHtmlA = await clientA.GetStringAsync("/list");
        var (id, rowVersion) = ExtractFirstItemInfo(listHtmlA);

        var listHtmlB = await clientB.GetStringAsync("/list");
        var tokenB = WebTestHelpers.ExtractAntiForgeryToken(listHtmlB);

        var response = await clientB.PostAsync($"/list/items/{id}/toggle", new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["__RequestVerificationToken"] = tokenB,
            ["rowVersion"] = rowVersion
        }));
        response.EnsureSuccessStatusCode();

        var finalHtmlA = await clientA.GetStringAsync("/list");
        Assert.True(HasStatusBadge(finalHtmlA, "Needed"), "Expected the item to still show a Needed status badge.");
        Assert.False(HasStatusBadge(finalHtmlA, "Purchased"), "The other household's toggle should not have changed this item's status.");
    }

    [Fact]
    public async Task ToggleItem_AjaxRequestWithStaleRowVersionReturnsConflict()
    {
        var client = _factory.CreateClient();
        await WebTestHelpers.RegisterAsync(client, "conflict1@example.com", "Conflict User");
        await WebTestHelpers.CreateHouseholdAsync(client, "Conflict Household");
        await AddItemAsync(client, "Butter");

        var listHtml = await client.GetStringAsync("/list");
        var (id, _) = ExtractFirstItemInfo(listHtml);
        var token = WebTestHelpers.ExtractAntiForgeryToken(listHtml);

        var request = new HttpRequestMessage(HttpMethod.Post, $"/list/items/{id}/toggle")
        {
            Content = new FormUrlEncodedContent(new Dictionary<string, string>
            {
                ["__RequestVerificationToken"] = token,
                ["rowVersion"] = Guid.NewGuid().ToString()
            })
        };
        request.Headers.Add("X-Requested-With", "XMLHttpRequest");

        var response = await client.SendAsync(request);

        Assert.Equal(HttpStatusCode.Conflict, response.StatusCode);
    }

    private static async Task AddItemAsync(HttpClient client, string displayName)
    {
        var html = await client.GetStringAsync("/list");
        var token = WebTestHelpers.ExtractAntiForgeryToken(html);

        var response = await client.PostAsync("/list/items", new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["__RequestVerificationToken"] = token,
            ["NewItem.DisplayName"] = displayName
        }));
        response.EnsureSuccessStatusCode();
    }

    private static bool HasStatusBadge(string html, string status)
    {
        return Regex.IsMatch(html, $"badge bg-secondary\">{Regex.Escape(status)}</span>");
    }

    private static (string Id, string RowVersion) ExtractFirstItemInfo(string html)
    {
        var idMatch = Regex.Match(html, "id=\"list-item-([0-9a-fA-F-]{36})\"");
        if (!idMatch.Success)
        {
            throw new InvalidOperationException("No list item found in HTML.");
        }

        var window = html.Substring(idMatch.Index, Math.Min(1200, html.Length - idMatch.Index));
        var rowVersionMatch = Regex.Match(window, "name=\"rowVersion\" value=\"([^\"]+)\"");
        if (!rowVersionMatch.Success)
        {
            throw new InvalidOperationException("No rowVersion field found for the list item.");
        }

        return (idMatch.Groups[1].Value, rowVersionMatch.Groups[1].Value);
    }
}
