class Holding::ForwardCalculator
  attr_reader :account

  def initialize(account, security_ids: nil)
    @account = account
    @security_ids = security_ids
    # Track cost basis per security: { security_id => { total_cost: BigDecimal, total_qty: BigDecimal } }
    @cost_basis_tracker = Hash.new { |h, k| h[k] = { total_cost: BigDecimal("0"), total_qty: BigDecimal("0") } }
  end

  def calculate
    Rails.logger.tagged("Holding::ForwardCalculator") do
      current_portfolio = generate_starting_portfolio
      next_portfolio = {}
      holdings = []

      account.start_date.upto(Date.current).each do |date|
        trades = portfolio_cache.get_trades(date: date)
        update_cost_basis_tracker(trades)
        next_portfolio = transform_portfolio(current_portfolio, trades, direction: :forward)
        holdings.concat(build_holdings(next_portfolio, date))
        current_portfolio = next_portfolio
      end

      Holding.gapfill(holdings)
    end
  end

  private
    def portfolio_cache
      @portfolio_cache ||= Holding::PortfolioCache.new(account, security_ids: @security_ids)
    end

    def empty_portfolio
      securities = portfolio_cache.get_securities
      securities.each_with_object({}) { |security, hash| hash[security.id] = 0 }
    end

    def generate_starting_portfolio
      empty_portfolio
    end

    def transform_portfolio(previous_portfolio, trade_entries, direction: :forward)
      new_quantities = previous_portfolio.dup

      trade_entries.each do |trade_entry|
        trade = trade_entry.entryable
        security_id = trade.security_id
        qty_change = trade.qty
        qty_change = qty_change * -1 if direction == :reverse
        new_quantities[security_id] = (new_quantities[security_id] || 0) + qty_change
      end

      new_quantities
    end

    def build_holdings(portfolio, date, price_source: nil)
      portfolio.map do |security_id, qty|
        next if @security_ids && !@security_ids.include?(security_id)

        price = portfolio_cache.get_price(security_id, date, source: price_source)

        if price.nil?
          next
        end

        Holding.new(
          account_id: account.id,
          security_id: security_id,
          date: date,
          qty: qty,
          price: price.price,
          currency: price.currency,
          amount: qty * price.price,
          cost_basis: cost_basis_for(security_id, price.currency)
        )
      end.compact
    end

    # Updates cost basis tracker with buy trades (qty > 0)
    # Uses weighted average cost method
    def update_cost_basis_tracker(trade_entries)
      trade_entries.each do |trade_entry|
        trade = trade_entry.entryable
        next unless trade.qty > 0 # Only track buys

        security_id = trade.security_id
        tracker = @cost_basis_tracker[security_id]

        # Convert trade price to account currency if needed
        trade_price = Money.new(trade.price, trade.currency)
        begin
          converted_price = trade_price.exchange_to(account.currency).amount
        rescue Money::ConversionError
          converted_price = trade.price
        end

        tracker[:total_cost] += converted_price * trade.qty
        tracker[:total_qty] += trade.qty
      end
    end

    # Returns the current cost basis for a security, or nil if no buys recorded
    def cost_basis_for(security_id, currency)
      tracker = @cost_basis_tracker[security_id]
      return nil if tracker[:total_qty].zero?

      tracker[:total_cost] / tracker[:total_qty]
    end
end
