using System.Collections.ObjectModel;

namespace BudgetApp.Domain.Models;

/// <summary>
/// Ledger aggregate containing all ledger entries.
/// Provides calculations and operations on entries.
/// </summary>
public class Ledger
{
    private readonly List<LedgerEntry> _entries;

    public ReadOnlyCollection<LedgerEntry> Entries => _entries.AsReadOnly();

    public Ledger()
    {
        _entries = new List<LedgerEntry>();
    }

    public Ledger(IEnumerable<LedgerEntry> entries)
    {
        if (entries == null)
            throw new ArgumentNullException(nameof(entries));

        _entries = new List<LedgerEntry>(entries);
    }

    /// <summary>
    /// Adds a ledger entry to the ledger.
    /// </summary>
    public void AddEntry(LedgerEntry entry)
    {
        if (entry == null)
            throw new ArgumentNullException(nameof(entry));

        entry.Validate();
        _entries.Add(entry);
    }

    /// <summary>
    /// Removes a ledger entry from the ledger.
    /// </summary>
    public void RemoveEntry(Guid entryId)
    {
        var entry = _entries.FirstOrDefault(e => e.Id == entryId);
        if (entry != null)
        {
            _entries.Remove(entry);
        }
    }

    /// <summary>
    /// Gets all entries of a specific type.
    /// </summary>
    public IEnumerable<LedgerEntry> GetEntriesByType(EntryType type)
    {
        return _entries.Where(e => e.Type == type);
    }

    /// <summary>
    /// Gets entries within a date range.
    /// </summary>
    public IEnumerable<LedgerEntry> GetEntriesByDateRange(DateTime startDate, DateTime endDate)
    {
        return _entries.Where(e => e.Date >= startDate && e.Date <= endDate);
    }

    /// <summary>
    /// Calculates the total amount for entries of a specific type.
    /// </summary>
    public Money CalculateTotalByType(EntryType type, string currency)
    {
        var total = _entries
            .Where(e => e.Type == type && e.Amount.Currency == currency)
            .Sum(e => e.Amount.Amount);

        return new Money(total, currency);
    }

    /// <summary>
    /// Calculates the total income minus total expenses (net balance).
    /// </summary>
    public Money CalculateNetBalance(string currency)
    {
        var income = CalculateTotalByType(EntryType.Income, currency);
        var expenses = CalculateTotalByType(EntryType.Expense, currency);

        var netAmount = income.Amount - expenses.Amount;
        return new Money(Math.Max(0, netAmount), currency);
    }

    /// <summary>
    /// Gets all entries that allocate to a specific budget.
    /// </summary>
    public IEnumerable<LedgerEntry> GetEntriesForBudget(Guid budgetId)
    {
        return _entries.Where(e => e.BudgetAllocations.ContainsKey(budgetId));
    }

    /// <summary>
    /// Calculates the total amount allocated to a specific budget from all entries.
    /// </summary>
    public Money CalculateTotalAllocatedToBudget(Guid budgetId, string currency)
    {
        var total = _entries
            .Where(e => e.BudgetAllocations.ContainsKey(budgetId))
            .Sum(e =>
            {
                if (!e.BudgetAllocations.TryGetValue(budgetId, out var allocation))
                    return 0;

                if (allocation.Currency != currency)
                    return 0;

                // Income adds to budget, expenses subtract
                return e.Type == EntryType.Income ? allocation.Amount : -allocation.Amount;
            });

        return new Money(Math.Max(0, total), currency);
    }
}

