module LedgerTestingHelper
  def create_account_with_ledger(account:, entries: [], exchange_rates: [], security_prices: [], holdings: [])
    # Clear all exchange rates and security prices to ensure clean test environment
    ExchangeRate.destroy_all
    Security::Price.destroy_all

    # Create account with specified attributes
    account_attrs = account.except(:type)
    account_type = account[:type]

    # Create the account
    created_account = families(:empty).accounts.create!(
      name: "Test Account",
      accountable: account_type.new,
      balance: account[:balance] || 0, # Doesn't matter, ledger derives this
      cash_balance: account[:cash_balance] || 0, # Doesn't matter, ledger derives this
      **account_attrs
    )

    # Set up exchange rates if provided
    exchange_rates.each do |rate_data|
      ExchangeRate.create!(
        date: rate_data[:date],
        from_currency: rate_data[:from],
        to_currency: rate_data[:to],
        rate: rate_data[:rate]
      )
    end

    # Set up security prices if provided
    security_prices.each do |price_data|
      security = Security.find_or_create_by!(ticker: price_data[:ticker]) do |s|
        s.name = price_data[:ticker]
      end

      Security::Price.create!(
        security: security,
        date: price_data[:date],
        price: price_data[:price],
        currency: created_account.currency
      )
    end

    # Create entries in the order they were specified
    entries.each do |entry_data|
      case entry_data[:type]
      when "current_anchor", "opening_anchor", "reconciliation"
        # Create valuation entry
        created_account.entries.create!(
          name: "Valuation",
          date: entry_data[:date],
          amount: entry_data[:balance],
          currency: entry_data[:currency] || created_account.currency,
          entryable: Valuation.new(kind: entry_data[:type])
        )
      when "transaction"
        # Use account currency if not specified
        currency = entry_data[:currency] || created_account.currency

        created_account.entries.create!(
          name: "Transaction",
          date: entry_data[:date],
          amount: entry_data[:amount],
          currency: currency,
          entryable: Transaction.new
        )
      when "trade"
        # Find or create security
        security = Security.find_or_create_by!(ticker: entry_data[:ticker]) do |s|
          s.name = entry_data[:ticker]
        end

        # Use account currency if not specified
        currency = entry_data[:currency] || created_account.currency

        trade = Trade.new(
          qty: entry_data[:qty],
          security: security,
          price: entry_data[:price],
          currency: currency,
          investment_activity_label: entry_data[:qty] > 0 ? "Buy" : "Sell"
        )

        created_account.entries.create!(
          name: "Trade",
          date: entry_data[:date],
          amount: entry_data[:qty] * entry_data[:price],
          currency: currency,
          entryable: trade
        )
      end
    end

    # Create holdings if provided
    holdings.each do |holding_data|
      # Find or create security
      security = Security.find_or_create_by!(ticker: holding_data[:ticker]) do |s|
        s.name = holding_data[:ticker]
      end

      Holding.create!(
        account: created_account,
        security: security,
        date: holding_data[:date],
        qty: holding_data[:qty],
        price: holding_data[:price],
        amount: holding_data[:amount],
        currency: holding_data[:currency] || created_account.currency
      )
    end

    created_account
  end

  def assert_calculated_ledger_balances(calculated_data:, expected_data:)
    # Convert expected data to a hash for easier lookup
    # Structure: [ { date:, legacy_balances: { balance:, cash_balance: }, balances: { start:, start_cash:, etc... }, flows: { ... }, adjustments: { ... } } ]
    expected_hash = {}
    expected_data.each do |data|
      expected_hash[data[:date].to_date] = {
        legacy_balances: data[:legacy_balances] || {},
        balances: data[:balances] || {},
        flows: data[:flows] || {},
        adjustments: data[:adjustments] || {}
      }
    end

    # Get all unique dates from all data sources
    all_dates = (calculated_data.map(&:date) + expected_hash.keys).uniq.sort

    # Check each date
    all_dates.each do |date|
      calculated_balance = calculated_data.find { |b| b.date == date }
      expected = expected_hash[date]

      if expected
        assert calculated_balance, "Expected balance for #{date} but none was calculated"

        # Always assert flows_factor is correct based on account classification
        expected_flows_factor = calculated_balance.account.classification == "asset" ? 1 : -1
        assert_equal expected_flows_factor, calculated_balance.flows_factor,
          "Flows factor mismatch for #{date}: expected #{expected_flows_factor} for #{calculated_balance.account.classification} account"

        legacy_balances = expected[:legacy_balances]
        balances = expected[:balances]
        flows = expected[:flows]
        adjustments = expected[:adjustments]

        # Legacy balance assertions
        if legacy_balances.any?
          assert_equal legacy_balances[:balance], calculated_balance.balance,
            "Balance mismatch for #{date}"

          assert_equal legacy_balances[:cash_balance], calculated_balance.cash_balance,
            "Cash balance mismatch for #{date}"
        end

        # Balance assertions
        if balances.any?
          assert_equal balances[:start_cash], calculated_balance.start_cash_balance,
            "Start cash balance mismatch for #{date}" if balances.key?(:start_cash)

          assert_equal balances[:start_non_cash], calculated_balance.start_non_cash_balance,
            "Start non-cash balance mismatch for #{date}" if balances.key?(:start_non_cash)

          # Calculate end_cash_balance using the formula from the migration
          if balances.key?(:end_cash)
            # Determine flows_factor based on account classification
            flows_factor = calculated_balance.account.classification == "asset" ? 1 : -1
            expected_end_cash = calculated_balance.start_cash_balance +
                               ((calculated_balance.cash_inflows - calculated_balance.cash_outflows) * flows_factor) +
                               calculated_balance.cash_adjustments
            assert_equal balances[:end_cash], expected_end_cash,
              "End cash balance mismatch for #{date}"
          end

          # Calculate end_non_cash_balance using the formula from the migration
          if balances.key?(:end_non_cash)
            # Determine flows_factor based on account classification
            flows_factor = calculated_balance.account.classification == "asset" ? 1 : -1
            expected_end_non_cash = calculated_balance.start_non_cash_balance +
                                   ((calculated_balance.non_cash_inflows - calculated_balance.non_cash_outflows) * flows_factor) +
                                   calculated_balance.net_market_flows +
                                   calculated_balance.non_cash_adjustments
            assert_equal balances[:end_non_cash], expected_end_non_cash,
              "End non-cash balance mismatch for #{date}"
          end

          # Calculate start_balance using the formula from the migration
          if balances.key?(:start)
            expected_start = calculated_balance.start_cash_balance + calculated_balance.start_non_cash_balance
            assert_equal balances[:start], expected_start,
              "Start balance mismatch for #{date}"
          end

          # Calculate end_balance using the formula from the migration since we're not persisting balances,
          # and generated columns are not available until the record is persisted
          if balances.key?(:end)
            # Determine flows_factor based on account classification
            flows_factor = calculated_balance.account.classification == "asset" ? 1 : -1
            expected_end_cash_component = calculated_balance.start_cash_balance +
                                         ((calculated_balance.cash_inflows - calculated_balance.cash_outflows) * flows_factor) +
                                         calculated_balance.cash_adjustments
            expected_end_non_cash_component = calculated_balance.start_non_cash_balance +
                                             ((calculated_balance.non_cash_inflows - calculated_balance.non_cash_outflows) * flows_factor) +
                                             calculated_balance.net_market_flows +
                                             calculated_balance.non_cash_adjustments
            expected_end = expected_end_cash_component + expected_end_non_cash_component
            assert_equal balances[:end], expected_end,
              "End balance mismatch for #{date}"
          end
        end

        # Flow assertions
        # If flows passed is 0, we assert all columns are 0
        if flows.is_a?(Integer) && flows == 0
          assert_equal 0, calculated_balance.cash_inflows,
            "Cash inflows mismatch for #{date}"

          assert_equal 0, calculated_balance.cash_outflows,
            "Cash outflows mismatch for #{date}"

          assert_equal 0, calculated_balance.non_cash_inflows,
            "Non-cash inflows mismatch for #{date}"

          assert_equal 0, calculated_balance.non_cash_outflows,
            "Non-cash outflows mismatch for #{date}"

          assert_equal 0, calculated_balance.net_market_flows,
            "Net market flows mismatch for #{date}"
        elsif flows.is_a?(Hash) && flows.any?
          # Cash flows - must be asserted together
          if flows.key?(:cash_inflows) || flows.key?(:cash_outflows)
            assert flows.key?(:cash_inflows) && flows.key?(:cash_outflows),
              "Cash inflows and outflows must be asserted together for #{date}"

            assert_equal flows[:cash_inflows], calculated_balance.cash_inflows,
              "Cash inflows mismatch for #{date}"

            assert_equal flows[:cash_outflows], calculated_balance.cash_outflows,
              "Cash outflows mismatch for #{date}"
          end

          # Non-cash flows - must be asserted together
          if flows.key?(:non_cash_inflows) || flows.key?(:non_cash_outflows)
            assert flows.key?(:non_cash_inflows) && flows.key?(:non_cash_outflows),
              "Non-cash inflows and outflows must be asserted together for #{date}"

            assert_equal flows[:non_cash_inflows], calculated_balance.non_cash_inflows,
              "Non-cash inflows mismatch for #{date}"

            assert_equal flows[:non_cash_outflows], calculated_balance.non_cash_outflows,
              "Non-cash outflows mismatch for #{date}"
          end

          # Market flows - can be asserted independently
          if flows.key?(:net_market_flows)
            assert_equal flows[:net_market_flows], calculated_balance.net_market_flows,
              "Net market flows mismatch for #{date}"
          end
        end

        # Adjustment assertions
        if adjustments.is_a?(Integer) && adjustments == 0
          assert_equal 0, calculated_balance.cash_adjustments,
            "Cash adjustments mismatch for #{date}"

          assert_equal 0, calculated_balance.non_cash_adjustments,
            "Non-cash adjustments mismatch for #{date}"
        elsif adjustments.is_a?(Hash) && adjustments.any?
          assert_equal adjustments[:cash_adjustments], calculated_balance.cash_adjustments,
            "Cash adjustments mismatch for #{date}" if adjustments.key?(:cash_adjustments)

          assert_equal adjustments[:non_cash_adjustments], calculated_balance.non_cash_adjustments,
            "Non-cash adjustments mismatch for #{date}" if adjustments.key?(:non_cash_adjustments)
        end

        # Temporary assertions during migration (remove after migration complete)
        # TODO: Remove these assertions after migration is complete
        # Since we're not persisting balances, we calculate the end values
        flows_factor = calculated_balance.account.classification == "asset" ? 1 : -1
        expected_end_cash = calculated_balance.start_cash_balance +
                           ((calculated_balance.cash_inflows - calculated_balance.cash_outflows) * flows_factor) +
                           calculated_balance.cash_adjustments
        expected_end_balance = expected_end_cash +
                              calculated_balance.start_non_cash_balance +
                              ((calculated_balance.non_cash_inflows - calculated_balance.non_cash_outflows) * flows_factor) +
                              calculated_balance.net_market_flows +
                              calculated_balance.non_cash_adjustments

        assert_equal calculated_balance.cash_balance, expected_end_cash,
          "Temporary assertion failed: end_cash_balance should equal cash_balance for #{date}"

        assert_equal calculated_balance.balance, expected_end_balance,
          "Temporary assertion failed: end_balance should equal balance for #{date}"
      else
        assert_nil calculated_balance, "Unexpected balance calculated for #{date}"
      end
    end

    # Verify we got all expected dates
    expected_dates = expected_hash.keys.sort
    calculated_dates = calculated_data.map(&:date).sort

    expected_dates.each do |date|
      assert_includes calculated_dates, date,
        "Expected balance for #{date} was not in calculated data"
    end
  end
end
