using System.Net.Http.Json;
using FluentAssertions;
using foodmarket.IntegrationTests.Support;
using Microsoft.AspNetCore.SignalR.Client;
using Xunit;

namespace foodmarket.IntegrationTests;

/// <summary>SignalR-хаб per-org. После Post-a розничной продажи владелец org
/// получает событие <c>SalePosted</c>, владелец другой org — НЕТ (multi-tenant).
/// Тест поднимает реальный API через WebApplicationFactory, использует TestServer
/// в качестве handler'a SignalR-клиента.</summary>
[Collection(ApiCollection.Name)]
public class SignalRNotificationsTests
{
    private readonly ApiFactory _factory;
    public SignalRNotificationsTests(ApiFactory factory) => _factory = factory;

    [Fact]
    public async Task Sale_posted_emits_event_to_own_org_only()
    {
        var actorA = new ApiActor(_factory.CreateClient());
        var emailA = $"sigA-{Guid.NewGuid():N}@example.kz";
        (await actorA.SignupAsync(emailA, "Passw0rd!", "SigA Org")).EnsureSuccessStatusCode();
        var tokenA = await actorA.TokenAsync(emailA, "Passw0rd!");
        actorA.UseToken(tokenA);

        var actorB = new ApiActor(_factory.CreateClient());
        var emailB = $"sigB-{Guid.NewGuid():N}@example.kz";
        (await actorB.SignupAsync(emailB, "Passw0rd!", "SigB Org")).EnsureSuccessStatusCode();
        var tokenB = await actorB.TokenAsync(emailB, "Passw0rd!");
        actorB.UseToken(tokenB);

        // Поднимаем хаб для обеих org через WebApplicationFactory-handler.
        var hubA = await BuildHubAsync(tokenA);
        var hubB = await BuildHubAsync(tokenB);

        var aGotSale = new TaskCompletionSource<System.Text.Json.JsonElement>();
        var bGotSale = new TaskCompletionSource<System.Text.Json.JsonElement>();
        hubA.On<System.Text.Json.JsonElement>("SalePosted", p => aGotSale.TrySetResult(p));
        hubB.On<System.Text.Json.JsonElement>("SalePosted", p => bGotSale.TrySetResult(p));

        // Засеем у A продукт + остаток через приёмку.
        var (productId, storeId) = await SeedProductWithStockAsync(actorA);

        // A создаёт чек и проводит. Currency / retail-point — те же что уже есть в bootstrap'е.
        var currencyId = (await actorA.GetJsonAsync("/api/catalog/currencies"))
            .GetProperty("items").EnumerateArray()
            .First(x => x.GetProperty("code").GetString() == "KZT")
            .GetProperty("id").GetString();
        var retailPointId = (await actorA.GetJsonAsync("/api/catalog/retail-points"))
            .GetProperty("items").EnumerateArray().First()
            .GetProperty("id").GetString();

        var saleResp = await actorA.Http.PostAsJsonAsync("/api/sales/retail", new
        {
            date = DateTime.UtcNow,
            storeId = storeId,
            retailPointId = retailPointId,
            currencyId = currencyId,
            payment = 0,
            isReturn = false,
            lines = new[] { new { productId = productId, quantity = 1, unitPrice = 100m, discount = 0m, vatPercent = 12m } },
            subtotal = 100m,
            discountTotal = 0m,
            total = 100m,
            paidCash = 100m,
            paidCard = 0m,
        });
        saleResp.EnsureSuccessStatusCode();
        var sale = (await saleResp.Content.ReadFromJsonAsync<System.Text.Json.JsonElement>())!;
        var saleId = sale.GetProperty("id").GetString();
        var postResp = await actorA.Http.PostAsync($"/api/sales/retail/{saleId}/post", null);
        postResp.EnsureSuccessStatusCode();

        // Ждём событие у A (timeout 8с — Hub queue + SignalR handshake).
        var aEvt = await Task.WhenAny(aGotSale.Task, Task.Delay(TimeSpan.FromSeconds(8)));
        aEvt.Should().Be(aGotSale.Task, "A должен был получить SalePosted");
        var payload = await aGotSale.Task;
        // Поля camelCase (System.Text.Json default).
        payload.GetProperty("number").GetString().Should().NotBeNullOrEmpty();
        payload.GetProperty("total").GetDecimal().Should().Be(100m);

        // B не должен получить ничего за то же время.
        var bEvt = await Task.WhenAny(bGotSale.Task, Task.Delay(TimeSpan.FromSeconds(2)));
        bEvt.Should().NotBe(bGotSale.Task, "B НЕ должен получать события другой org (multi-tenant leak)");

        await hubA.StopAsync();
        await hubB.StopAsync();
    }

    private async Task<HubConnection> BuildHubAsync(string token)
    {
        var conn = new HubConnectionBuilder()
            .WithUrl("http://localhost/hubs/notifications", o =>
            {
                o.HttpMessageHandlerFactory = _ => _factory.Server.CreateHandler();
                o.AccessTokenProvider = () => Task.FromResult<string?>(token);
            })
            .Build();
        await conn.StartAsync();
        return conn;
    }

    private async Task<(string productId, string storeId)> SeedProductWithStockAsync(ApiActor actor)
    {
        var units = (await actor.GetJsonAsync("/api/catalog/units-of-measure?pageSize=200"))
            .GetProperty("items").EnumerateArray().First(x => x.GetProperty("code").GetString() == "796");
        var groups = (await actor.GetJsonAsync("/api/catalog/product-groups"))
            .GetProperty("items").EnumerateArray().First();
        var priceTypes = (await actor.GetJsonAsync("/api/catalog/price-types"))
            .GetProperty("items").EnumerateArray()
            .First(x => x.GetProperty("isRetail").GetBoolean());
        var currencies = (await actor.GetJsonAsync("/api/catalog/currencies"))
            .GetProperty("items").EnumerateArray()
            .First(x => x.GetProperty("code").GetString() == "KZT");
        var stores = (await actor.GetJsonAsync("/api/catalog/stores"))
            .GetProperty("items").EnumerateArray()
            .First(x => x.GetProperty("isMain").GetBoolean());
        var storeId = stores.GetProperty("id").GetString()!;

        var prodResp = await actor.Http.PostAsJsonAsync("/api/catalog/products", new
        {
            name = "SignalR test", article = $"SR-{Guid.NewGuid():N}",
            unitOfMeasureId = units.GetProperty("id").GetString(),
            vat = 12, vatEnabled = true,
            productGroupId = groups.GetProperty("id").GetString(),
            packaging = 1,
            prices = new[] { new { priceTypeId = priceTypes.GetProperty("id").GetString(), amount = 100m, currencyId = currencies.GetProperty("id").GetString() } },
            barcodes = new[] { new { code = "5000000000010", type = 1, isPrimary = true } },
        });
        prodResp.EnsureSuccessStatusCode();
        var prod = (await prodResp.Content.ReadFromJsonAsync<System.Text.Json.JsonElement>())!;
        var productId = prod.GetProperty("id").GetString()!;

        var supplierResp = await actor.Http.PostAsJsonAsync("/api/catalog/counterparties", new { name = "Supp SR", type = 2 });
        supplierResp.EnsureSuccessStatusCode();
        var supplier = (await supplierResp.Content.ReadFromJsonAsync<System.Text.Json.JsonElement>())!;
        var supplierId = supplier.GetProperty("id").GetString()!;

        var supplyResp = await actor.Http.PostAsJsonAsync("/api/purchases/supplies", new
        {
            date = DateTime.UtcNow,
            supplierId = supplierId,
            storeId = storeId,
            currencyId = currencies.GetProperty("id").GetString(),
            lines = new[] { new { productId = productId, quantity = 10m, unitPrice = 50m } },
        });
        supplyResp.EnsureSuccessStatusCode();
        var supply = (await supplyResp.Content.ReadFromJsonAsync<System.Text.Json.JsonElement>())!;
        var supplyId = supply.GetProperty("id").GetString()!;
        (await actor.Http.PostAsync($"/api/purchases/supplies/{supplyId}/post", null)).EnsureSuccessStatusCode();

        return (productId, storeId);
    }
}
