namespace BudgetApp.Tests.Domain;

using BudgetApp.Domain.Models;
using Xunit;

public class LedgerTests
{
    [Fact]
    public void Constructor_CreatesEmptyLedger()
    {
        // Arrange & Act
        var ledger = new Ledger();

        // Assert
        Assert.Empty(ledger.Entries);
    }

    [Fact]
    public void AddEntry_WithValidEntry_AddsToLedger()
    {
        // Arrange
        var ledger = new Ledger();
        var budgetAllocations = new Dictionary<Guid, Money>
        {
            { Guid.NewGuid(), new Money(100m, "USD") }
        };
        var entry = new LedgerEntry(
            DateTime.Now,
            "Grocery Shopping",
            new Money(100m, "USD"),
            EntryType.Expense,
            budgetAllocations);

        // Act
        ledger.AddEntry(entry);

        // Assert
        Assert.Single(ledger.Entries);
        Assert.Equal(entry, ledger.Entries.First());
    }

    [Fact]
    public void AddEntry_WithInvalidEntry_ThrowsException()
    {
        // Arrange
        var ledger = new Ledger();
        var budgetAllocations = new Dictionary<Guid, Money>
        {
            { Guid.NewGuid(), new Money(50m, "USD") }
        };

        // Act & Assert - The exception is thrown during construction, not when adding
        Assert.Throws<InvalidOperationException>(() => new LedgerEntry(
            Guid.NewGuid(),
            DateTime.Now,
            "Grocery Shopping",
            new Money(100m, "USD"),
            EntryType.Expense,
            budgetAllocations));
    }

    [Fact]
    public void GetEntriesByType_ReturnsOnlyMatchingType()
    {
        // Arrange
        var ledger = new Ledger();
        var expenseAllocations = new Dictionary<Guid, Money> { { Guid.NewGuid(), new Money(100m, "USD") } };
        var incomeAllocations = new Dictionary<Guid, Money> { { Guid.NewGuid(), new Money(1000m, "USD") } };

        var expense = new LedgerEntry(DateTime.Now, "Expense", new Money(100m, "USD"), EntryType.Expense, expenseAllocations);
        var income = new LedgerEntry(DateTime.Now, "Income", new Money(1000m, "USD"), EntryType.Income, incomeAllocations);

        ledger.AddEntry(expense);
        ledger.AddEntry(income);

        // Act
        var expenses = ledger.GetEntriesByType(EntryType.Expense);
        var incomes = ledger.GetEntriesByType(EntryType.Income);

        // Assert
        Assert.Single(expenses);
        Assert.Single(incomes);
        Assert.Equal(EntryType.Expense, expenses.First().Type);
        Assert.Equal(EntryType.Income, incomes.First().Type);
    }

    [Fact]
    public void CalculateTotalByType_ReturnsCorrectTotal()
    {
        // Arrange
        var ledger = new Ledger();
        var expenseAllocations1 = new Dictionary<Guid, Money> { { Guid.NewGuid(), new Money(100m, "USD") } };
        var expenseAllocations2 = new Dictionary<Guid, Money> { { Guid.NewGuid(), new Money(200m, "USD") } };

        var expense1 = new LedgerEntry(DateTime.Now, "Expense 1", new Money(100m, "USD"), EntryType.Expense, expenseAllocations1);
        var expense2 = new LedgerEntry(DateTime.Now, "Expense 2", new Money(200m, "USD"), EntryType.Expense, expenseAllocations2);

        ledger.AddEntry(expense1);
        ledger.AddEntry(expense2);

        // Act
        var total = ledger.CalculateTotalByType(EntryType.Expense, "USD");

        // Assert
        Assert.Equal(300m, total.Amount);
    }

    [Fact]
    public void CalculateNetBalance_ReturnsIncomeMinusExpenses()
    {
        // Arrange
        var ledger = new Ledger();
        var expenseAllocations = new Dictionary<Guid, Money> { { Guid.NewGuid(), new Money(100m, "USD") } };
        var incomeAllocations = new Dictionary<Guid, Money> { { Guid.NewGuid(), new Money(1000m, "USD") } };

        var expense = new LedgerEntry(DateTime.Now, "Expense", new Money(100m, "USD"), EntryType.Expense, expenseAllocations);
        var income = new LedgerEntry(DateTime.Now, "Income", new Money(1000m, "USD"), EntryType.Income, incomeAllocations);

        ledger.AddEntry(expense);
        ledger.AddEntry(income);

        // Act
        var netBalance = ledger.CalculateNetBalance("USD");

        // Assert
        Assert.Equal(900m, netBalance.Amount);
    }

    [Fact]
    public void GetEntriesForBudget_ReturnsEntriesWithBudgetAllocation()
    {
        // Arrange
        var budgetId = Guid.NewGuid();
        var ledger = new Ledger();
        var allocations1 = new Dictionary<Guid, Money> { { budgetId, new Money(100m, "USD") } };
        var allocations2 = new Dictionary<Guid, Money> { { Guid.NewGuid(), new Money(200m, "USD") } };

        var entry1 = new LedgerEntry(DateTime.Now, "Entry 1", new Money(100m, "USD"), EntryType.Expense, allocations1);
        var entry2 = new LedgerEntry(DateTime.Now, "Entry 2", new Money(200m, "USD"), EntryType.Expense, allocations2);

        ledger.AddEntry(entry1);
        ledger.AddEntry(entry2);

        // Act
        var entries = ledger.GetEntriesForBudget(budgetId);

        // Assert
        Assert.Single(entries);
        Assert.Equal(entry1, entries.First());
    }
}

