using CartWise.Application.Services;
using CartWise.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace CartWise.Application.Tests;

public class PurchaseServiceTests : IDisposable
{
    private static readonly DateTime Now = new(2026, 8, 10, 12, 0, 0, DateTimeKind.Utc);

    private readonly CartWiseDbContext _db;
    private readonly PurchaseService _sut;

    public PurchaseServiceTests()
    {
        var options = new DbContextOptionsBuilder<CartWiseDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;

        _db = new CartWiseDbContext(options);
        _sut = new PurchaseService(_db, TimeProvider.System);
    }

    public void Dispose()
    {
        _db.Dispose();
        GC.SuppressFinalize(this);
    }

    [Fact]
    public async Task StartPurchaseAsync_CreatesPurchase()
    {
        var householdId = Guid.NewGuid();

        var result = await _sut.StartPurchaseAsync(householdId, "user-1", Now);

        Assert.True(result.IsSuccess);
        Assert.Equal(householdId, result.Value!.HouseholdId);
        Assert.False(result.Value.IsCompleted);
    }

    [Fact]
    public async Task AddPurchaseItemAsync_AddsItemAndPersistsIt()
    {
        var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now);

        var result = await _sut.AddPurchaseItemAsync(started.Value!.HouseholdId, started.Value.PurchaseId, "Milk", 3.99m);

        Assert.True(result.IsSuccess);
        Assert.Equal("Milk", result.Value!.Description);

        var reloaded = await _sut.GetPurchaseAsync(started.Value.HouseholdId, started.Value.PurchaseId);
        Assert.Single(reloaded!.Items);
    }

    [Fact]
    public async Task AddPurchaseItemAsync_FailsForPurchaseInAnotherHousehold()
    {
        var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now);

        var result = await _sut.AddPurchaseItemAsync(Guid.NewGuid(), started.Value!.PurchaseId, "Milk", 3.99m);

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task AddPurchaseItemAsync_FailsWhenPurchaseAlreadyCompleted()
    {
        var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now);
        await _sut.AddPurchaseItemAsync(started.Value!.HouseholdId, started.Value.PurchaseId, "Milk", 3.99m);
        await _sut.CompletePurchaseAsync(started.Value.HouseholdId, started.Value.PurchaseId);

        var result = await _sut.AddPurchaseItemAsync(started.Value.HouseholdId, started.Value.PurchaseId, "Bread", 2.5m);

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task CompletePurchaseAsync_ComputesSubtotalAndTotal()
    {
        var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now);
        await _sut.AddPurchaseItemAsync(started.Value!.HouseholdId, started.Value.PurchaseId, "Milk", 3.99m);
        await _sut.AddPurchaseItemAsync(started.Value.HouseholdId, started.Value.PurchaseId, "Bread", 2.50m);

        var result = await _sut.CompletePurchaseAsync(started.Value.HouseholdId, started.Value.PurchaseId, tax: 0.50m);

        Assert.True(result.IsSuccess);
        Assert.Equal(6.49m, result.Value!.Subtotal);
        Assert.Equal(0.50m, result.Value.Tax);
        Assert.Equal(6.99m, result.Value.Total);
    }

    [Fact]
    public async Task CompletePurchaseAsync_FailsForPurchaseInAnotherHousehold()
    {
        var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now);

        var result = await _sut.CompletePurchaseAsync(Guid.NewGuid(), started.Value!.PurchaseId);

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task GetHouseholdPurchasesAsync_ReturnsOnlyThatHouseholdsPurchasesNewestFirst()
    {
        var householdA = Guid.NewGuid();
        var householdB = Guid.NewGuid();
        await _sut.StartPurchaseAsync(householdA, "user-1", Now.AddDays(-1));
        await _sut.StartPurchaseAsync(householdA, "user-1", Now);
        await _sut.StartPurchaseAsync(householdB, "user-2", Now);

        var results = await _sut.GetHouseholdPurchasesAsync(householdA);

        Assert.Equal(2, results.Count);
        Assert.True(results[0].PurchasedUtc >= results[1].PurchasedUtc);
    }
}
