# The current system calculates a single, end-of-day balance every day for each account for simplicity.
# In most cases, this is sufficient.  However, for the "Activity View", we need to show intraday balances
# to show users how each entry affects their balances.  This class calculates intraday balances by
# interpolating between end-of-day balances.
class Account::BalanceTrendCalculator
  BalanceTrend = Struct.new(:trend, :cash, keyword_init: true)

  class << self
    def for(entries)
      return nil if entries.blank?

      account = entries.first.account

      date_range = entries.minmax_by(&:date)
      min_entry_date, max_entry_date = date_range.map(&:date)

      # In case view is filtered and there are entry gaps, refetch all entries in range
      all_entries = account.entries.where(date: min_entry_date..max_entry_date).chronological.to_a
      balances = account.balances.where(date: (min_entry_date - 1.day)..max_entry_date).chronological.to_a
      holdings = account.holdings.where(date: (min_entry_date - 1.day)..max_entry_date).to_a

      new(all_entries, balances, holdings)
    end
  end

  def initialize(entries, balances, holdings)
    @entries = entries
    @balances = balances
    @holdings = holdings
  end

  def trend_for(entry)
    intraday_balance = nil
    intraday_cash_balance = nil

    start_of_day_balance = balances.find { |b| b.date == entry.date - 1.day && b.currency == entry.currency }
    end_of_day_balance = balances.find { |b| b.date == entry.date && b.currency == entry.currency }

    return BalanceTrend.new(trend: nil) if start_of_day_balance.blank? || end_of_day_balance.blank?

    todays_holdings_value = holdings.select { |h| h.date == entry.date }.sum(&:amount)

    prior_balance = start_of_day_balance.balance
    prior_cash_balance = start_of_day_balance.cash_balance
    current_balance = nil
    current_cash_balance = nil

    todays_entries = entries.select { |e| e.date == entry.date }

    todays_entries.each_with_index do |e, idx|
      if e.account_valuation?
        current_balance = e.amount
        current_cash_balance = e.amount
      else
        multiplier = e.account.liability? ? 1 : -1
        balance_change = e.account_trade? ? 0 : multiplier * e.amount
        cash_change = multiplier * e.amount

        current_balance = prior_balance + balance_change
        current_cash_balance = prior_cash_balance + cash_change
      end

      if e.id == entry.id
        # Final entry should always match the end-of-day balances
        if idx == todays_entries.size - 1
          intraday_balance = end_of_day_balance.balance
          intraday_cash_balance = end_of_day_balance.cash_balance
        else
          intraday_balance = current_balance
          intraday_cash_balance = current_cash_balance
        end

        break
      else
        prior_balance = current_balance
        prior_cash_balance = current_cash_balance
      end
    end

    return BalanceTrend.new(trend: nil) unless intraday_balance.present?

    BalanceTrend.new(
      trend: TimeSeries::Trend.new(
        current: Money.new(intraday_balance, entry.currency),
        previous: Money.new(prior_balance, entry.currency),
        favorable_direction: entry.account.favorable_direction
      ),
      cash: Money.new(intraday_cash_balance, entry.currency),
    )
  end

  private
    attr_reader :entries, :balances, :holdings
end
