namespace BudgetApp.Domain.Models;

/// <summary>
/// Represents a ledger entry for tracking money in/out of budgets.
/// </summary>
public class LedgerEntry
{
    public Guid Id { get; protected set; }
    public DateTime Date { get; protected set; }
    public string Description { get; protected set; } = string.Empty;
    public Money Amount { get; protected set; } = null!;
    public EntryType Type { get; protected set; }
    public Dictionary<Guid, Money> BudgetAllocations { get; protected set; }

    protected LedgerEntry()
    {
        BudgetAllocations = new Dictionary<Guid, Money>();
    }

    public LedgerEntry(
        Guid id,
        DateTime date,
        string description,
        Money amount,
        EntryType type,
        Dictionary<Guid, Money> budgetAllocations)
    {
        if (string.IsNullOrWhiteSpace(description))
            throw new ArgumentException("Description cannot be null or empty.", nameof(description));
        
        if (amount == null)
            throw new ArgumentNullException(nameof(amount));
        
        if (budgetAllocations == null)
            throw new ArgumentNullException(nameof(budgetAllocations));

        Id = id;
        Date = date;
        Description = description;
        Amount = amount;
        Type = type;
        BudgetAllocations = new Dictionary<Guid, Money>(budgetAllocations);
        
        Validate();
    }

    public LedgerEntry(
        DateTime date,
        string description,
        Money amount,
        EntryType type,
        Dictionary<Guid, Money> budgetAllocations)
        : this(Guid.NewGuid(), date, description, amount, type, budgetAllocations)
    {
    }

    /// <summary>
    /// Validates that budget allocations sum to the entry amount.
    /// </summary>
    public void Validate()
    {
        if (Type == EntryType.Expense)
        {
            ValidateExpenseBalance();
        }
        else if (Type == EntryType.Income)
        {
            ValidateIncomeBalance();
        }
    }

    /// <summary>
    /// Validates that expense budget allocations sum to the expense amount.
    /// </summary>
    private void ValidateExpenseBalance()
    {
        if (BudgetAllocations.Count == 0)
            throw new InvalidOperationException("Expense must have at least one budget allocation.");

        var totalAllocated = BudgetAllocations.Values
            .Aggregate(new Money(0, Amount.Currency), (sum, money) => sum.Add(money));

        if (totalAllocated.Amount != Amount.Amount)
            throw new InvalidOperationException(
                $"Expense amount ({Amount.Amount}) does not match sum of budget allocations ({totalAllocated.Amount}).");
    }

    /// <summary>
    /// Validates that income budget allocations sum to the income amount.
    /// </summary>
    private void ValidateIncomeBalance()
    {
        if (BudgetAllocations.Count == 0)
            throw new InvalidOperationException("Income must have at least one budget allocation.");

        var totalAllocated = BudgetAllocations.Values
            .Aggregate(new Money(0, Amount.Currency), (sum, money) => sum.Add(money));

        if (totalAllocated.Amount != Amount.Amount)
            throw new InvalidOperationException(
                $"Income amount ({Amount.Amount}) does not match sum of budget allocations ({totalAllocated.Amount}).");
    }
}

/// <summary>
/// Type of ledger entry.
/// </summary>
public enum EntryType
{
    Income,
    Expense
}

