using System.Net.Http.Json;
using System.Text.Json;
using FluentAssertions;
using foodmarket.IntegrationTests.Support;
using Xunit;

namespace foodmarket.IntegrationTests;

[Collection(ApiCollection.Name)]
public class ConcurrencyTokenTests
{
    private readonly ApiFactory _factory;
    public ConcurrencyTokenTests(ApiFactory factory) => _factory = factory;
    private static string RandomBarcode()
        => string.Concat(Enumerable.Range(0, 13).Select(_ => Random.Shared.Next(0, 10)));

    /// <summary>Два параллельных PUT с одинаковым xmin: первый успешен (204),
    /// второй падает 409 (concurrency_conflict). После 409 клиент должен
    /// перезагрузить документ и попробовать снова — это тестируем третим PUT.</summary>
    [Fact]
    public async Task Two_parallel_updates_with_same_xmin_one_wins_other_gets_409()
    {
        var api = new ApiActor(_factory.CreateClient());
        await api.SignupAndLoginAsync($"conc-{Guid.NewGuid():N}");
        var refs = await api.LoadRefsAsync();
        var supplierId = await api.CreateCounterpartyAsync($"S-{Guid.NewGuid():N}");
        var p1 = await api.CreateProductAsync(refs, $"P-{Guid.NewGuid():N}", 100m, RandomBarcode());

        var create = await api.Http.PostAsJsonAsync("/api/purchases/supplies", new
        {
            date = DateTime.UtcNow, supplierId, storeId = refs.StoreId, currencyId = refs.CurrencyId,
            notes = "initial",
            lines = new[] { new { productId = p1, quantity = 1m, unitPrice = 50m } },
        });
        create.EnsureSuccessStatusCode();
        var json = await create.Content.ReadFromJsonAsync<JsonElement>();
        var supplyId = json.GetProperty("id").GetString();
        var xmin = json.GetProperty("xmin").GetUInt32();
        xmin.Should().BeGreaterThan(0u);

        // Build two identical PUTs with the SAME xmin.
        var body1 = new
        {
            date = DateTime.UtcNow, supplierId, storeId = refs.StoreId, currencyId = refs.CurrencyId,
            notes = "writer-1",
            lines = new[] { new { productId = p1, quantity = 2m, unitPrice = 50m } },
            xmin,
        };
        var body2 = new
        {
            date = DateTime.UtcNow, supplierId, storeId = refs.StoreId, currencyId = refs.CurrencyId,
            notes = "writer-2",
            lines = new[] { new { productId = p1, quantity = 3m, unitPrice = 50m } },
            xmin,
        };

        // Sequential — гарантированно один 204, второй 409. Параллельный race в
        // Postgres даёт тот же исход, но интеграционный тест предпочитает
        // воспроизводимый порядок (а не «обычно зелёный»).
        using var r1 = await api.Http.PutAsJsonAsync($"/api/purchases/supplies/{supplyId}", body1);
        ((int)r1.StatusCode).Should().Be(204, await r1.Content.ReadAsStringAsync());

        using var r2 = await api.Http.PutAsJsonAsync($"/api/purchases/supplies/{supplyId}", body2);
        ((int)r2.StatusCode).Should().Be(409);
        var body = await r2.Content.ReadAsStringAsync();
        body.Should().Contain("concurrency_conflict");

        // GET → видим writer-1 результат, xmin обновился.
        var after = await api.GetJsonAsync($"/api/purchases/supplies/{supplyId}");
        after.GetProperty("notes").GetString().Should().Be("writer-1");
        var newXmin = after.GetProperty("xmin").GetUInt32();
        newXmin.Should().NotBe(xmin);

        // Retry с новым xmin — снова успех.
        using var r3 = await api.Http.PutAsJsonAsync($"/api/purchases/supplies/{supplyId}", new
        {
            date = DateTime.UtcNow, supplierId, storeId = refs.StoreId, currencyId = refs.CurrencyId,
            notes = "writer-2-retry",
            lines = new[] { new { productId = p1, quantity = 3m, unitPrice = 50m } },
            xmin = newXmin,
        });
        ((int)r3.StatusCode).Should().Be(204);
    }

    /// <summary>Старый клиент без поля xmin продолжает работать — Update без
    /// concurrency-проверки (legacy compatibility).</summary>
    [Fact]
    public async Task Update_without_xmin_field_works_legacy_compat()
    {
        var api = new ApiActor(_factory.CreateClient());
        await api.SignupAndLoginAsync($"conc-leg-{Guid.NewGuid():N}");
        var refs = await api.LoadRefsAsync();
        var supplierId = await api.CreateCounterpartyAsync($"S-{Guid.NewGuid():N}");
        var p1 = await api.CreateProductAsync(refs, $"P-{Guid.NewGuid():N}", 100m, RandomBarcode());

        var create = await api.Http.PostAsJsonAsync("/api/purchases/supplies", new
        {
            date = DateTime.UtcNow, supplierId, storeId = refs.StoreId, currencyId = refs.CurrencyId,
            notes = "init",
            lines = new[] { new { productId = p1, quantity = 1m, unitPrice = 50m } },
        });
        create.EnsureSuccessStatusCode();
        var supplyId = (await create.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();

        // PUT без xmin — должен пройти.
        using var r = await api.Http.PutAsJsonAsync($"/api/purchases/supplies/{supplyId}", new
        {
            date = DateTime.UtcNow, supplierId, storeId = refs.StoreId, currencyId = refs.CurrencyId,
            notes = "no-xmin",
            lines = new[] { new { productId = p1, quantity = 2m, unitPrice = 50m } },
        });
        ((int)r.StatusCode).Should().Be(204);
    }
}
