class BalanceSheet
  include Monetizable

  monetize :total_assets, :total_liabilities, :net_worth

  attr_reader :family

  def initialize(family)
    @family = family
  end

  def total_assets
    totals_query.filter { |t| t.classification == "asset" }.sum(&:converted_balance)
  end

  def total_liabilities
    totals_query.filter { |t| t.classification == "liability" }.sum(&:converted_balance)
  end

  def net_worth
    total_assets - total_liabilities
  end

  def classification_groups
    [
      ClassificationGroup.new(
        key: "asset",
        display_name: "Assets",
        icon: "blocks",
        account_groups: account_groups("asset")
      ),
      ClassificationGroup.new(
        key: "liability",
        display_name: "Debts",
        icon: "scale",
        account_groups: account_groups("liability")
      )
    ]
  end

  def account_groups(classification = nil)
    classification_accounts = classification ? totals_query.filter { |t| t.classification == classification } : totals_query
    classification_total = classification_accounts.sum(&:converted_balance)
    account_groups = classification_accounts.group_by(&:accountable_type).transform_keys { |k| Accountable.from_type(k) }

    account_groups.map do |accountable, accounts|
      group_total = accounts.sum(&:converted_balance)

      AccountGroup.new(
        key: accountable.model_name.param_key,
        name: accountable.display_name,
        classification: accountable.classification,
        total: group_total,
        total_money: Money.new(group_total, currency),
        weight: classification_total.zero? ? 0 : group_total / classification_total.to_d * 100,
        missing_rates?: accounts.any? { |a| a.missing_rates? },
        color: accountable.color,
        accounts: accounts.map do |account|
          account.define_singleton_method(:weight) do
            classification_total.zero? ? 0 : account.converted_balance / classification_total.to_d * 100
          end
          account
        end.sort_by(&:weight).reverse
      )
    end.sort_by(&:weight).reverse
  end

  def net_worth_series(period: Period.last_30_days)
    active_accounts.balance_series(currency: currency, period: period, favorable_direction: "up")
  end

  def currency
    family.currency
  end

  private
    ClassificationGroup = Struct.new(:key, :display_name, :icon, :account_groups, keyword_init: true)
    AccountGroup = Struct.new(:key, :name, :accountable_type, :classification, :total, :total_money, :weight, :accounts, :color, :missing_rates?, keyword_init: true)

    def active_accounts
      family.accounts.active.with_attached_logo
    end

    def totals_query
      @totals_query ||= active_accounts
            .joins(ActiveRecord::Base.sanitize_sql_array([ "LEFT JOIN exchange_rates ON exchange_rates.date = CURRENT_DATE AND accounts.currency = exchange_rates.from_currency AND exchange_rates.to_currency = ?", currency ]))
            .select(
              "accounts.*",
              "SUM(accounts.balance * COALESCE(exchange_rates.rate, 1)) as converted_balance",
              ActiveRecord::Base.sanitize_sql_array([ "COUNT(CASE WHEN accounts.currency <> ? AND exchange_rates.rate IS NULL THEN 1 END) as missing_rates", currency ])
            )
            .group(:classification, :accountable_type, :id)
            .to_a
    end
end
