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 TransferPostUnpostTests
{
    private readonly ApiFactory _factory;
    public TransferPostUnpostTests(ApiFactory factory) => _factory = factory;

    private static string RandomBarcode()
        => string.Concat(Enumerable.Range(0, 13).Select(_ => Random.Shared.Next(0, 10)));

    /// <summary>Создать второй склад через api (главный уже есть из bootstrap).</summary>
    private async Task<string> CreateSecondStore(ApiActor api, string name)
    {
        var resp = await api.Http.PostAsJsonAsync("/api/catalog/stores", new
        {
            name, code = (string?)null, address = "Алматы 2",
            phone = (string?)null, managerName = (string?)null,
            isMain = false, isActive = true,
        });
        resp.EnsureSuccessStatusCode();
        return (await resp.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString()!;
    }

    [Fact]
    public async Task Post_creates_paired_movements_unpost_reverts_no_orphans()
    {
        var api = new ApiActor(_factory.CreateClient());
        await api.SignupAndLoginAsync($"xfer-{Guid.NewGuid():N}");
        var refs = await api.LoadRefsAsync();
        var to = await CreateSecondStore(api, $"Склад-2-{Guid.NewGuid():N}");
        var productId = await api.CreateProductAsync(refs, $"Prod-{Guid.NewGuid():N}", 200m, RandomBarcode());

        // Оприходовать 10 шт на первый склад.
        var enter = await api.Http.PostAsJsonAsync("/api/inventory/enters", new
        {
            date = DateTime.UtcNow, storeId = refs.StoreId, currencyId = refs.CurrencyId,
            notes = "seed", lines = new[] { new { productId, quantity = 10m, unitCost = 50m } },
        });
        enter.EnsureSuccessStatusCode();
        var enterId = (await enter.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();
        (await api.Http.PostAsJsonAsync($"/api/inventory/enters/{enterId}/post", new { })).EnsureSuccessStatusCode();
        (await api.StockOfAsync(refs.StoreId, productId)).Should().Be(10m);

        // Создать перемещение: 4 шт с первого склада на второй.
        var xfer = await api.Http.PostAsJsonAsync("/api/inventory/transfers", new
        {
            date = DateTime.UtcNow,
            fromStoreId = refs.StoreId, toStoreId = to,
            notes = "test",
            lines = new[] { new { productId, quantity = 4m, unitCost = 50m } },
        });
        xfer.EnsureSuccessStatusCode();
        var xferId = (await xfer.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();

        // Post → 6 на источнике, 4 на получателе.
        using var post = await api.Http.PostAsJsonAsync($"/api/inventory/transfers/{xferId}/post", new { });
        post.IsSuccessStatusCode.Should().BeTrue($"post вернул {(int)post.StatusCode}: {await post.Content.ReadAsStringAsync()}");
        (await api.StockOfAsync(refs.StoreId, productId)).Should().Be(6m);
        (await api.StockOfAsync(to, productId)).Should().Be(4m);

        // Проверяем что движений ровно 2 (TransferOut + TransferIn) — никаких orphan.
        var movs = await api.ListAsync($"/api/inventory/movements?productId={productId}&pageSize=100");
        var byThisDoc = movs.Where(m =>
            m.GetProperty("documentId").ValueKind == JsonValueKind.String &&
            m.GetProperty("documentId").GetString() == xferId).ToList();
        byThisDoc.Should().HaveCount(2, "перемещение порождает строго пару движений Out+In");

        // Unpost → 10 и 0. Атомарность: ИЛИ оба отменены, ИЛИ ни одного.
        using var unpost = await api.Http.PostAsJsonAsync($"/api/inventory/transfers/{xferId}/unpost", new { });
        unpost.IsSuccessStatusCode.Should().BeTrue();
        (await api.StockOfAsync(refs.StoreId, productId)).Should().Be(10m);
        (await api.StockOfAsync(to, productId)).Should().Be(0m);

        // По документу теперь 4 движения (2 forward + 2 reversal), но stock-инвариант держится.
        movs = await api.ListAsync($"/api/inventory/movements?productId={productId}&pageSize=100");
        var byThisDocAfter = movs.Where(m =>
            m.GetProperty("documentId").ValueKind == JsonValueKind.String &&
            m.GetProperty("documentId").GetString() == xferId).ToList();
        byThisDocAfter.Should().HaveCount(4);
    }

    [Fact]
    public async Task Cannot_create_transfer_to_same_store()
    {
        var api = new ApiActor(_factory.CreateClient());
        await api.SignupAndLoginAsync($"xfer-same-{Guid.NewGuid():N}");
        var refs = await api.LoadRefsAsync();
        var productId = await api.CreateProductAsync(refs, $"Prod-{Guid.NewGuid():N}", 100m, RandomBarcode());

        using var resp = await api.Http.PostAsJsonAsync("/api/inventory/transfers", new
        {
            date = DateTime.UtcNow,
            fromStoreId = refs.StoreId, toStoreId = refs.StoreId,
            notes = "same",
            lines = new[] { new { productId, quantity = 1m, unitCost = 10m } },
        });
        ((int)resp.StatusCode).Should().Be(400);
    }

    [Fact]
    public async Task Post_blocked_when_from_store_short()
    {
        var api = new ApiActor(_factory.CreateClient());
        await api.SignupAndLoginAsync($"xfer-short-{Guid.NewGuid():N}");
        var refs = await api.LoadRefsAsync();
        var to = await CreateSecondStore(api, $"Склад-S-{Guid.NewGuid():N}");
        var productId = await api.CreateProductAsync(refs, $"Prod-{Guid.NewGuid():N}", 100m, RandomBarcode());

        // На первом 0 шт; пытаемся перевести 5 — должно отбиться.
        var xfer = await api.Http.PostAsJsonAsync("/api/inventory/transfers", new
        {
            date = DateTime.UtcNow,
            fromStoreId = refs.StoreId, toStoreId = to,
            notes = "short",
            lines = new[] { new { productId, quantity = 5m, unitCost = 10m } },
        });
        xfer.EnsureSuccessStatusCode();
        var xferId = (await xfer.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();

        using var post = await api.Http.PostAsJsonAsync($"/api/inventory/transfers/{xferId}/post", new { });
        ((int)post.StatusCode).Should().Be(409);
        // Никаких движений не должно появиться.
        (await api.StockOfAsync(refs.StoreId, productId)).Should().Be(0m);
        (await api.StockOfAsync(to, productId)).Should().Be(0m);
    }

    [Fact]
    public async Task Tenant_isolation_transfer()
    {
        var a = new ApiActor(_factory.CreateClient());
        var b = new ApiActor(_factory.CreateClient());
        await a.SignupAndLoginAsync($"xfer-iso-a-{Guid.NewGuid():N}");
        await b.SignupAndLoginAsync($"xfer-iso-b-{Guid.NewGuid():N}");
        var refsA = await a.LoadRefsAsync();
        var toA = await CreateSecondStore(a, $"Склад-A2-{Guid.NewGuid():N}");
        var productA = await a.CreateProductAsync(refsA, $"Prod-{Guid.NewGuid():N}", 100m, RandomBarcode());

        var resp = await a.Http.PostAsJsonAsync("/api/inventory/transfers", new
        {
            date = DateTime.UtcNow,
            fromStoreId = refsA.StoreId, toStoreId = toA,
            notes = "iso",
            lines = new[] { new { productId = productA, quantity = 1m, unitCost = 10m } },
        });
        resp.EnsureSuccessStatusCode();
        var xferId = (await resp.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();

        var bList = await b.ListAsync("/api/inventory/transfers?pageSize=200");
        bList.Should().NotContain(x => x.GetProperty("id").GetString() == xferId);

        using var direct = await b.Http.GetAsync($"/api/inventory/transfers/{xferId}");
        direct.StatusCode.Should().Be(System.Net.HttpStatusCode.NotFound);
    }
}
