using CartWise.Domain.Entities;
using CartWise.Domain.Enums;

namespace CartWise.Domain.Tests;

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

    [Fact]
    public void Constructor_DefaultsToManualSourceAndNoStore()
    {
        var purchase = new Purchase(Guid.NewGuid(), "user-1", Now, Now);

        Assert.Equal(PurchaseSourceType.Manual, purchase.SourceType);
        Assert.Null(purchase.StoreLocationId);
        Assert.Empty(purchase.Items);
        Assert.False(purchase.IsCompleted);
    }

    [Fact]
    public void Constructor_ThrowsWhenHouseholdIdIsEmpty()
    {
        Assert.Throws<ArgumentException>(() => new Purchase(Guid.Empty, "user-1", Now, Now));
    }

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData(null)]
    public void Constructor_ThrowsWhenPurchasedByUserIdIsMissing(string? userId)
    {
        Assert.Throws<ArgumentException>(() => new Purchase(Guid.NewGuid(), userId!, Now, Now));
    }

    [Fact]
    public void AddItem_DefaultsQuantityToOne()
    {
        var purchase = new Purchase(Guid.NewGuid(), "user-1", Now, Now);

        var item = purchase.AddItem("Milk", 3.99m, Now);

        Assert.Equal(1m, item.Quantity);
        Assert.Equal(3.99m, item.LinePrice);
        Assert.Single(purchase.Items);
    }

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData(null)]
    public void AddItem_ThrowsWhenDescriptionIsMissing(string? description)
    {
        var purchase = new Purchase(Guid.NewGuid(), "user-1", Now, Now);

        Assert.Throws<ArgumentException>(() => purchase.AddItem(description!, 3.99m, Now));
    }

    [Fact]
    public void AddItem_ThrowsWhenQuantityIsZeroOrNegative()
    {
        var purchase = new Purchase(Guid.NewGuid(), "user-1", Now, Now);

        Assert.Throws<ArgumentOutOfRangeException>(() => purchase.AddItem("Milk", 3.99m, Now, quantity: 0));
        Assert.Throws<ArgumentOutOfRangeException>(() => purchase.AddItem("Milk", 3.99m, Now, quantity: -1));
    }

    [Fact]
    public void AddItem_ThrowsWhenLinePriceIsNegative()
    {
        var purchase = new Purchase(Guid.NewGuid(), "user-1", Now, Now);

        Assert.Throws<ArgumentOutOfRangeException>(() => purchase.AddItem("Milk", -1m, Now));
    }

    [Fact]
    public void AddItem_ThrowsOnCompletedPurchase()
    {
        var purchase = new Purchase(Guid.NewGuid(), "user-1", Now, Now);
        purchase.AddItem("Milk", 3.99m, Now);
        purchase.Complete();

        Assert.Throws<InvalidOperationException>(() => purchase.AddItem("Bread", 2.5m, Now));
    }

    [Fact]
    public void Complete_SumsLinePricesIntoSubtotalAndTotal()
    {
        var purchase = new Purchase(Guid.NewGuid(), "user-1", Now, Now);
        purchase.AddItem("Milk", 3.99m, Now);
        purchase.AddItem("Bread", 2.50m, Now);

        purchase.Complete();

        Assert.Equal(6.49m, purchase.Subtotal);
        Assert.Equal(6.49m, purchase.Total);
        Assert.Null(purchase.Tax);
        Assert.True(purchase.IsCompleted);
    }

    [Fact]
    public void Complete_AddsTaxToTotalWhenProvided()
    {
        var purchase = new Purchase(Guid.NewGuid(), "user-1", Now, Now);
        purchase.AddItem("Milk", 10m, Now);

        purchase.Complete(tax: 0.80m);

        Assert.Equal(10m, purchase.Subtotal);
        Assert.Equal(0.80m, purchase.Tax);
        Assert.Equal(10.80m, purchase.Total);
    }

    [Fact]
    public void Complete_ThrowsWhenAlreadyCompleted()
    {
        var purchase = new Purchase(Guid.NewGuid(), "user-1", Now, Now);
        purchase.AddItem("Milk", 3.99m, Now);
        purchase.Complete();

        Assert.Throws<InvalidOperationException>(() => purchase.Complete());
    }
}
