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

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

    /// <summary>Создаёт 2 проведённых чека на разные товары и проверяет, что
    /// groupBy=product отдаёт обе позиции с правильной выручкой.</summary>
    [Fact]
    public async Task Group_by_product_returns_revenue_per_item()
    {
        var api = new ApiActor(_factory.CreateClient());
        await api.SignupAndLoginAsync($"rpt-{Guid.NewGuid():N}");
        var refs = await api.LoadRefsAsync();
        var p1 = await api.CreateProductAsync(refs, $"P1-{Guid.NewGuid():N}", 200m, RandomBarcode());
        var p2 = await api.CreateProductAsync(refs, $"P2-{Guid.NewGuid():N}", 300m, RandomBarcode());

        // Принять по 10 шт каждый.
        foreach (var (pid, price, cost) in new[] { (p1, 200m, 100m), (p2, 300m, 150m) })
        {
            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 = pid, quantity = 10m, unitCost = cost } },
            });
            enter.EnsureSuccessStatusCode();
            var eid = (await enter.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();
            (await api.Http.PostAsJsonAsync($"/api/inventory/enters/{eid}/post", new { })).EnsureSuccessStatusCode();
        }

        // Чек 1: 2 шт p1 (400) + 1 шт p2 (300) = 700
        await PostSale(api, refs, new[]
        {
            (p1, 2m, 200m),
            (p2, 1m, 300m),
        }, paid: 700m);

        // Чек 2: 3 шт p1 (600)
        await PostSale(api, refs, new[] { (p1, 3m, 200m) }, paid: 600m);

        var rows = await api.GetJsonAsync("/api/reports/sales?groupBy=product");
        var arr = rows.EnumerateArray().ToList();
        arr.Should().HaveCountGreaterOrEqualTo(2);

        var p1Row = arr.First(x => x.GetProperty("key").GetString() == p1);
        p1Row.GetProperty("revenue").GetDecimal().Should().Be(1000m, "5 шт по 200");
        p1Row.GetProperty("quantity").GetDecimal().Should().Be(5m);

        var p2Row = arr.First(x => x.GetProperty("key").GetString() == p2);
        p2Row.GetProperty("revenue").GetDecimal().Should().Be(300m);
    }

    [Fact]
    public async Task Group_by_payment_returns_per_method()
    {
        var api = new ApiActor(_factory.CreateClient());
        await api.SignupAndLoginAsync($"rpt-pm-{Guid.NewGuid():N}");
        var refs = await api.LoadRefsAsync();
        var p1 = await api.CreateProductAsync(refs, $"P-{Guid.NewGuid():N}", 100m, RandomBarcode());

        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 = p1, quantity = 20m, unitCost = 50m } },
        });
        enter.EnsureSuccessStatusCode();
        var eid = (await enter.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();
        (await api.Http.PostAsJsonAsync($"/api/inventory/enters/{eid}/post", new { })).EnsureSuccessStatusCode();

        // Cash на 200 + Card на 300.
        await PostSale(api, refs, new[] { (p1, 2m, 100m) }, paid: 200m, payment: 0);
        await PostSale(api, refs, new[] { (p1, 3m, 100m) }, paid: 0, paidCard: 300m, payment: 1);

        var rows = await api.GetJsonAsync("/api/reports/sales?groupBy=payment");
        var arr = rows.EnumerateArray().ToList();
        var cash = arr.First(x => x.GetProperty("key").GetString() == "0");
        var card = arr.First(x => x.GetProperty("key").GetString() == "1");
        cash.GetProperty("revenue").GetDecimal().Should().Be(200m);
        card.GetProperty("revenue").GetDecimal().Should().Be(300m);
    }

    [Fact]
    public async Task Returns_reduce_revenue_signed()
    {
        var api = new ApiActor(_factory.CreateClient());
        await api.SignupAndLoginAsync($"rpt-ret-{Guid.NewGuid():N}");
        var refs = await api.LoadRefsAsync();
        var p1 = await api.CreateProductAsync(refs, $"P-{Guid.NewGuid():N}", 100m, RandomBarcode());

        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 = p1, quantity = 5m, unitCost = 50m } },
        });
        enter.EnsureSuccessStatusCode();
        var eid = (await enter.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();
        (await api.Http.PostAsJsonAsync($"/api/inventory/enters/{eid}/post", new { })).EnsureSuccessStatusCode();

        var saleId = await PostSale(api, refs, new[] { (p1, 5m, 100m) }, paid: 500m);

        // Создаём возврат на 2 шт.
        var crt = await api.Http.PostAsJsonAsync($"/api/sales/retail/{saleId}/create-return", new { });
        crt.EnsureSuccessStatusCode();
        var retId = (await crt.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();
        // Уменьшаем возврат до 2 шт (по умолчанию подтянулось 5).
        var put = await api.Http.PutAsJsonAsync($"/api/sales/retail/{retId}", new
        {
            date = DateTime.UtcNow, storeId = refs.StoreId, retailPointId = (string?)null,
            customerId = (string?)null, currencyId = refs.CurrencyId,
            payment = 0, paidCash = 200m, paidCard = 0m, notes = "ret",
            lines = new[] { new { productId = p1, quantity = 2m, unitPrice = 100m, discount = 0m, vatPercent = 12m } },
        });
        put.EnsureSuccessStatusCode();
        (await api.Http.PostAsJsonAsync($"/api/sales/retail/{retId}/post", new { })).EnsureSuccessStatusCode();

        var rows = await api.GetJsonAsync("/api/reports/sales?groupBy=product");
        var p1Row = rows.EnumerateArray().First(x => x.GetProperty("key").GetString() == p1);
        // 5 шт по 100 минус 2 шт по 100 = 300.
        p1Row.GetProperty("revenue").GetDecimal().Should().Be(300m);
        p1Row.GetProperty("quantity").GetDecimal().Should().Be(3m);
    }

    [Fact]
    public async Task Tenant_isolation_sales_report()
    {
        var a = new ApiActor(_factory.CreateClient());
        var b = new ApiActor(_factory.CreateClient());
        await a.SignupAndLoginAsync($"rpt-iso-a-{Guid.NewGuid():N}");
        await b.SignupAndLoginAsync($"rpt-iso-b-{Guid.NewGuid():N}");
        var refsA = await a.LoadRefsAsync();
        var pA = await a.CreateProductAsync(refsA, $"PA-{Guid.NewGuid():N}", 100m, RandomBarcode());

        var enter = await a.Http.PostAsJsonAsync("/api/inventory/enters", new
        {
            date = DateTime.UtcNow, storeId = refsA.StoreId, currencyId = refsA.CurrencyId,
            notes = "seed", lines = new[] { new { productId = pA, quantity = 1m, unitCost = 10m } },
        });
        enter.EnsureSuccessStatusCode();
        var eid = (await enter.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();
        (await a.Http.PostAsJsonAsync($"/api/inventory/enters/{eid}/post", new { })).EnsureSuccessStatusCode();
        await PostSale(a, refsA, new[] { (pA, 1m, 100m) }, paid: 100m);

        // B запросил тот же отчёт — должен видеть пусто.
        var rowsB = await b.GetJsonAsync("/api/reports/sales?groupBy=product");
        rowsB.EnumerateArray().Should().NotContain(x => x.GetProperty("key").GetString() == pA);
    }

    [Fact]
    public async Task Csv_export_works()
    {
        var api = new ApiActor(_factory.CreateClient());
        await api.SignupAndLoginAsync($"rpt-csv-{Guid.NewGuid():N}");
        var refs = await api.LoadRefsAsync();
        var p1 = await api.CreateProductAsync(refs, $"P-{Guid.NewGuid():N}", 100m, RandomBarcode());

        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 = p1, quantity = 5m, unitCost = 50m } },
        });
        enter.EnsureSuccessStatusCode();
        var eid = (await enter.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();
        (await api.Http.PostAsJsonAsync($"/api/inventory/enters/{eid}/post", new { })).EnsureSuccessStatusCode();
        await PostSale(api, refs, new[] { (p1, 2m, 100m) }, paid: 200m);

        using var resp = await api.Http.GetAsync("/api/reports/sales/export?groupBy=product&format=csv");
        resp.EnsureSuccessStatusCode();
        resp.Content.Headers.ContentType!.MediaType.Should().Be("text/csv");
        var text = await resp.Content.ReadAsStringAsync();
        text.Should().Contain("Выручка"); // русский заголовок
        text.Should().Contain("200");
    }

    private async Task<string> PostSale(ApiActor api, ApiActor.Refs refs,
        (string ProductId, decimal Qty, decimal Price)[] lines,
        decimal paid, decimal paidCard = 0m, int payment = 0)
    {
        var resp = await api.Http.PostAsJsonAsync("/api/sales/retail", new
        {
            date = DateTime.UtcNow, storeId = refs.StoreId, retailPointId = (string?)null,
            customerId = (string?)null, currencyId = refs.CurrencyId,
            payment, paidCash = paid, paidCard,
            lines = lines.Select(l => new
            {
                productId = l.ProductId, quantity = l.Qty,
                unitPrice = l.Price, discount = 0m, vatPercent = 12m,
            }).ToArray(),
            notes = "test",
        });
        resp.EnsureSuccessStatusCode();
        var saleId = (await resp.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString()!;
        (await api.Http.PostAsJsonAsync($"/api/sales/retail/{saleId}/post", new { })).EnsureSuccessStatusCode();
        return saleId;
    }
}
