using FluentAssertions;
using foodmarket.Application.Inventory;
using foodmarket.Domain.Inventory;
using foodmarket.Infrastructure.Inventory;
using foodmarket.UnitTests.Support;
using Microsoft.EntityFrameworkCore;
using Xunit;

namespace foodmarket.UnitTests;

/// <summary>StockService.ApplyMovement: материализация Stock + запись StockMovement.
/// FK отключены — тестируем арифметику остатка и факт записи движения, не целостность.</summary>
public class StockServiceTests
{
    private static StockMovementDraft Draft(Guid product, Guid store, decimal qty) =>
        new(product, store, qty, MovementType.Supply, "supply",
            DocumentId: Guid.NewGuid(), DocumentNumber: "DOC-1", UnitCost: 100m, OccurredAt: DateTime.UtcNow);

    [Fact]
    public async Task Creates_stock_and_movement_on_first_application()
    {
        using var sqlite = new SqliteDb(foreignKeys: false);
        var org = Guid.NewGuid();
        var tenant = new FakeTenantContext { OrganizationId = org };
        var product = Guid.NewGuid();
        var store = Guid.NewGuid();

        decimal returned;
        using (var db = sqlite.Create(tenant))
        {
            returned = await new StockService(db, tenant).ApplyMovementAsync(Draft(product, store, 10m));
            await db.SaveChangesAsync();
        }

        returned.Should().Be(10m);
        using (var db = sqlite.Create(tenant))
        {
            var stock = await db.Stocks.SingleAsync(s => s.ProductId == product && s.StoreId == store);
            stock.Quantity.Should().Be(10m);
            stock.OrganizationId.Should().Be(org);
            (await db.StockMovements.CountAsync()).Should().Be(1);
        }
    }

    // Каждое проведение документа = свой SaveChanges, поэтому повторное движение
    // по тому же товару находит уже материализованный Stock и инкрементит его.
    [Fact]
    public async Task Second_application_increments_existing_stock()
    {
        using var sqlite = new SqliteDb(foreignKeys: false);
        var tenant = new FakeTenantContext { OrganizationId = Guid.NewGuid() };
        var product = Guid.NewGuid();
        var store = Guid.NewGuid();

        using var db = sqlite.Create(tenant);
        var svc = new StockService(db, tenant);

        await svc.ApplyMovementAsync(Draft(product, store, 10m));
        await db.SaveChangesAsync();
        var afterSecond = await svc.ApplyMovementAsync(Draft(product, store, 5m));
        await db.SaveChangesAsync();

        afterSecond.Should().Be(15m);
        (await db.Stocks.SingleAsync(s => s.ProductId == product && s.StoreId == store)).Quantity.Should().Be(15m);
        (await db.StockMovements.CountAsync()).Should().Be(2);
    }

    [Fact]
    public async Task Negative_movement_decrements_stock()
    {
        using var sqlite = new SqliteDb(foreignKeys: false);
        var tenant = new FakeTenantContext { OrganizationId = Guid.NewGuid() };
        var product = Guid.NewGuid();
        var store = Guid.NewGuid();

        using var db = sqlite.Create(tenant);
        var svc = new StockService(db, tenant);

        await svc.ApplyMovementAsync(Draft(product, store, 10m));
        await db.SaveChangesAsync();
        var afterSale = await svc.ApplyMovementAsync(Draft(product, store, -3m));
        await db.SaveChangesAsync();

        afterSale.Should().Be(7m);
    }

    [Fact]
    public async Task Throws_when_tenant_not_set()
    {
        using var sqlite = new SqliteDb(foreignKeys: false);
        var tenant = new FakeTenantContext { OrganizationId = null };
        using var db = sqlite.Create(tenant);
        var svc = new StockService(db, tenant);

        await FluentActions
            .Awaiting(() => svc.ApplyMovementAsync(Draft(Guid.NewGuid(), Guid.NewGuid(), 1m)))
            .Should().ThrowAsync<InvalidOperationException>();
    }
}
