namespace BudgetApp.Domain.Models;

/// <summary>
/// Goal-based budget with a target amount and date.
/// Can calculate if goal is achievable and required contribution rate.
/// </summary>
public class GoalBudget : Budget
{
    public Money GoalAmount { get; protected set; }
    public DateTime TargetDate { get; protected set; }
    public Money PeriodicContribution { get; protected set; }
    public TimeSpan ContributionPeriod { get; protected set; } // e.g., monthly, bi-weekly

    public GoalBudget(
        Guid id, 
        string name, 
        Money initialBalance, 
        Money goalAmount, 
        DateTime targetDate, 
        Money periodicContribution,
        TimeSpan contributionPeriod) 
        : base(id, name, initialBalance)
    {
        if (goalAmount == null)
            throw new ArgumentNullException(nameof(goalAmount));
        
        if (goalAmount.Amount <= 0)
            throw new ArgumentException("Goal amount must be greater than zero.", nameof(goalAmount));
        
        if (targetDate <= DateTime.Now)
            throw new ArgumentException("Target date must be in the future.", nameof(targetDate));
        
        if (periodicContribution == null)
            throw new ArgumentNullException(nameof(periodicContribution));
        
        if (periodicContribution.Amount < 0)
            throw new ArgumentException("Periodic contribution cannot be negative.", nameof(periodicContribution));

        if (initialBalance.Currency != goalAmount.Currency || 
            initialBalance.Currency != periodicContribution.Currency)
            throw new InvalidOperationException("All amounts must use the same currency.");

        GoalAmount = goalAmount;
        TargetDate = targetDate;
        PeriodicContribution = periodicContribution;
        ContributionPeriod = contributionPeriod;
    }

    public GoalBudget(
        string name, 
        Money initialBalance, 
        Money goalAmount, 
        DateTime targetDate, 
        Money periodicContribution,
        TimeSpan contributionPeriod) 
        : this(Guid.NewGuid(), name, initialBalance, goalAmount, targetDate, periodicContribution, contributionPeriod)
    {
    }

    /// <summary>
    /// Calculates if the goal is achievable with the current periodic contribution.
    /// </summary>
    public bool IsGoalAchievable(DateTime currentDate)
    {
        if (currentDate >= TargetDate)
            return Balance.Amount >= GoalAmount.Amount;

        var remainingAmount = GoalAmount.Amount - Balance.Amount;
        if (remainingAmount <= 0)
            return true;

        var timeRemaining = TargetDate - currentDate;
        var numberOfPeriods = (int)Math.Ceiling(timeRemaining.TotalDays / ContributionPeriod.TotalDays);
        
        if (numberOfPeriods <= 0)
            return false;

        var totalContributions = PeriodicContribution.Amount * numberOfPeriods;
        return totalContributions >= remainingAmount;
    }

    /// <summary>
    /// Calculates the required periodic contribution rate to achieve the goal.
    /// </summary>
    public Money CalculateRequiredContributionRate(DateTime currentDate)
    {
        if (currentDate >= TargetDate)
        {
            var remaining = GoalAmount.Amount - Balance.Amount;
            return remaining <= 0 
                ? new Money(0, Balance.Currency) 
                : new Money(remaining, Balance.Currency);
        }

        var remainingAmount = GoalAmount.Amount - Balance.Amount;
        if (remainingAmount <= 0)
            return new Money(0, Balance.Currency);

        var timeRemaining = TargetDate - currentDate;
        var numberOfPeriods = (int)Math.Ceiling(timeRemaining.TotalDays / ContributionPeriod.TotalDays);
        
        if (numberOfPeriods <= 0)
            throw new InvalidOperationException("Target date has already passed or is too close.");

        var requiredContribution = remainingAmount / numberOfPeriods;
        return new Money(requiredContribution, Balance.Currency);
    }
}

